# svelte-jsonschema-form ## Introduction svelte-jsonschema-form is a Svelte 5 library for creating forms based on JSON Schema, serving as an unofficial port of react-jsonschema-form. The library provides a powerful, type-safe form generation system that automatically renders form UI from JSON Schema definitions, eliminating the need to manually build form components. It supports complex schema features including nested objects, arrays, union types (anyOf/oneOf), conditional schemas, nullable schemas, and OpenAPI discriminators. The library offers extensive customization through multiple validator integrations (Ajv, Zod, Valibot, cfworker, schemasafe, standard-schema), theme systems (Basic, daisyUI v5, Flowbite v3, shadcn v4, Skeleton v4, SVAR v2), and flexible configuration options. It includes advanced features like field-level and form-level validation, async validation support, internationalization, custom widgets, field actions for value transformations, SvelteKit integration for SSR, caching utilities for performance optimization, and keyed array rendering for improved reactivity. ## API Documentation ### createForm - Primary Form Creation Function Creates a reactive form instance from a JSON Schema with full type safety and validation. ```typescript import { createForm, BasicForm, type Schema } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/basic-theme'; import '@sjsf/basic-theme/css/basic.css'; const schema: Schema = { title: 'User Registration', type: 'object', properties: { username: { type: 'string', title: 'Username', minLength: 3 }, email: { type: 'string', title: 'Email', format: 'email' }, age: { type: 'integer', title: 'Age', minimum: 18 }, subscribe: { type: 'boolean', title: 'Subscribe to newsletter', default: false } }, required: ['username', 'email'] }; const form = createForm({ theme, schema, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, initialValue: { subscribe: true }, onSubmit: (value) => { console.log('Form submitted:', value); // value is fully typed based on schema }, onSubmitError: (result, event, formState) => { console.error('Validation errors:', result.errors); // result.errors: Array<{ path: string[], message: string }> } }); ``` ### Array and Nested Object Schemas Handles complex nested structures with arrays and objects. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { title: 'Project Tasks', type: 'object', properties: { projectName: { type: 'string', title: 'Project Name' }, tasks: { type: 'array', title: 'Tasks', items: { type: 'object', properties: { name: { type: 'string', title: 'Task Name' }, priority: { type: 'string', title: 'Priority', enum: ['low', 'medium', 'high'], default: 'medium' }, completed: { type: 'boolean', title: 'Completed', default: false }, assignee: { type: 'object', title: 'Assignee', properties: { name: { type: 'string', title: 'Name' }, email: { type: 'string', format: 'email', title: 'Email' } } } }, required: ['name'] } } }, required: ['projectName'] }; const form = createForm({ ...defaults, schema, initialValue: { projectName: 'Q4 Release', tasks: [ { name: 'Setup environment', priority: 'high', completed: true, assignee: { name: 'Alice', email: 'alice@example.com' } } ] }, onSubmit: console.log }); ``` ### Zod Integration with adapt() Converts Zod schemas to SJSF configuration for type-safe validation. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { adapt } from '@sjsf/zod4-validator/classic'; import * as z from 'zod'; import * as defaults from './form-defaults'; const userSchema = z.object({ username: z.string() .min(3, 'Username must be at least 3 characters') .meta({ title: 'Username' }), email: z.string() .email('Invalid email address') .meta({ title: 'Email' }), age: z.number() .int() .min(18, 'Must be 18 or older') .optional() .meta({ title: 'Age' }), role: z.enum(['admin', 'user', 'guest']) .meta({ title: 'Role' }), preferences: z.object({ notifications: z.boolean().meta({ title: 'Enable Notifications' }), theme: z.enum(['light', 'dark']).meta({ title: 'Theme' }) }).meta({ title: 'Preferences' }) }).meta({ title: 'User Profile' }); // adapt() converts Zod schema to SJSF config const form = createForm({ ...defaults, ...adapt(userSchema), onSubmit: (value) => { // value is inferred from Zod schema with full type safety console.log('Validated user:', value); } }); ``` ### Field Validation Modes Configure when and how fields are validated with flexible timing options. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { ON_INPUT, ON_CHANGE, ON_BLUR, AFTER_CHANGED, AFTER_SUBMITTED } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { email: { type: 'string', format: 'email', title: 'Email' }, password: { type: 'string', minLength: 8, title: 'Password' } } }; const form = createForm({ ...defaults, schema, // Validate on blur, but only after user has changed the field fieldsValidationMode: ON_BLUR | AFTER_CHANGED, // Debounce validation by 300ms to avoid excessive validation calls fieldsValidationDebounceMs: 300, onSubmit: console.log, onFieldsValidationFailure: (state, config, value) => { console.error('Field validation failed:', state.error); } }); ``` ### Custom UI Schema for Layout and Widgets Override default rendering behavior with UI Schema customization. ```typescript import { createForm, BasicForm, type UiSchemaRoot } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { password: { type: 'string', title: 'Password' }, bio: { type: 'string', title: 'Biography' }, age: { type: 'integer', title: 'Age' }, agree: { type: 'boolean', title: 'I agree to terms' } } }; const uiSchema: UiSchemaRoot = { 'ui:globalOptions': { title: 'Advanced Settings' }, password: { 'ui:components': { stringField: 'passwordWidget' // Use password input } }, bio: { 'ui:components': { stringField: 'textareaWidget' // Use textarea }, 'ui:options': { title: 'Tell us about yourself' } }, age: { 'ui:components': { integerField: 'rangeWidget' // Use range slider } }, agree: { 'ui:components': { booleanField: 'switchWidget' // Use toggle switch } }, 'ui:order': ['password', 'age', 'bio', 'agree'] }; const form = createForm({ ...defaults, schema, uiSchema, onSubmit: console.log }); ``` ### Async Validation with Ajv Implement asynchronous validation for remote checks like username availability. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { createAsyncFormValidator } from '@sjsf/ajv8-validator'; import Ajv from 'ajv'; import * as defaults from './form-defaults'; // Setup Ajv with async validation support const ajv = new Ajv({ loadSchema: async (uri) => { // Load remote schemas if needed const response = await fetch(uri); return response.json(); } }); // Custom async format validator ajv.addFormat('unique-username', { async: true, validate: async (username: string) => { const response = await fetch(`/api/check-username?username=${username}`); const { available } = await response.json(); return available; } }); const schema: Schema = { type: 'object', properties: { username: { type: 'string', format: 'unique-username', title: 'Username' }, email: { type: 'string', format: 'email', title: 'Email' } }, required: ['username', 'email'] }; const form = createForm({ ...defaults, schema, validator: createAsyncFormValidator({ ajv }), onSubmit: (value) => { console.log('Valid and unique username:', value.username); } }); ``` ### Union Types with oneOf and anyOf Handle polymorphic schemas with discriminated unions. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { title: 'Payment Method', type: 'object', properties: { paymentType: { type: 'string', title: 'Payment Type', enum: ['card', 'bank', 'paypal'] } }, required: ['paymentType'], discriminator: { propertyName: 'paymentType', mapping: { card: '#/definitions/CardPayment', bank: '#/definitions/BankPayment', paypal: '#/definitions/PayPalPayment' } }, oneOf: [ { $ref: '#/definitions/CardPayment' }, { $ref: '#/definitions/BankPayment' }, { $ref: '#/definitions/PayPalPayment' } ], definitions: { CardPayment: { properties: { paymentType: { const: 'card' }, cardNumber: { type: 'string', title: 'Card Number' }, cvv: { type: 'string', title: 'CVV' }, expiry: { type: 'string', title: 'Expiry Date' } }, required: ['cardNumber', 'cvv', 'expiry'] }, BankPayment: { properties: { paymentType: { const: 'bank' }, accountNumber: { type: 'string', title: 'Account Number' }, routingNumber: { type: 'string', title: 'Routing Number' } }, required: ['accountNumber', 'routingNumber'] }, PayPalPayment: { properties: { paymentType: { const: 'paypal' }, email: { type: 'string', format: 'email', title: 'PayPal Email' } }, required: ['email'] } } }; const form = createForm({ ...defaults, schema, onSubmit: (value) => { console.log('Payment details:', value); } }); ``` ### SvelteKit Server Integration with Form Actions Integrate with SvelteKit form actions for progressive enhancement and SSR. ```typescript // +page.server.ts import { createFormValidator } from '@sjsf/ajv8-validator'; import { createFormIdBuilder } from '@sjsf/sveltekit'; import { fail } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; const schema = { type: 'object', properties: { username: { type: 'string', minLength: 3 }, email: { type: 'string', format: 'email' } }, required: ['username', 'email'] }; const validator = createFormValidator(); const idBuilder = createFormIdBuilder(); export const load: PageServerLoad = async () => { return { schema }; }; export const actions = { default: async ({ request }) => { const formData = await request.formData(); const data = Object.fromEntries(formData); const result = validator.validateFormValue(schema, data); if (result.errors) { return fail(400, { errors: result.errors, formData: data }); } // Process valid data console.log('Server received:', result.value); return { success: true, data: result.value }; } } satisfies Actions; ``` ```svelte { if (result.type === 'success') { console.log('Server validated data:', result.data); } }} onFailure={(result) => { console.error('Server validation failed:', result.data?.errors); }} /> ``` ### Custom Field Actions and Transformations Apply custom transformations and actions to field values before submission. ```typescript import { createForm, BasicForm, type UiSchemaRoot } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { amount: { type: 'number', title: 'Amount in dollars' }, tags: { type: 'string', title: 'Tags (comma-separated)' } } }; const uiSchema: UiSchemaRoot = { amount: { 'ui:options': { // Transform display value: cents to dollars action: { set: (value: number) => value / 100, get: (value: number) => value * 100 } } }, tags: { 'ui:options': { // Transform string to array and back action: { set: (value: string[]) => value?.join(', ') ?? '', get: (value: string) => value.split(',').map(s => s.trim()).filter(Boolean) } } } }; const form = createForm({ ...defaults, schema, uiSchema, initialValue: { amount: 2500, // stored as cents tags: ['javascript', 'svelte'] }, onSubmit: (value) => { console.log('Transformed value:', value); // { amount: 2500, tags: ['javascript', 'svelte'] } } }); ``` ### Focus Management on Validation Errors Automatically focus the first field with validation errors. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { focusOnFirstError } from '@sjsf/form/focus-on-first-error'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { firstName: { type: 'string', title: 'First Name' }, lastName: { type: 'string', title: 'Last Name' }, email: { type: 'string', format: 'email', title: 'Email' } }, required: ['firstName', 'lastName', 'email'] }; const form = createForm({ ...defaults, schema, onSubmitError: (result, event, formState) => { console.error('Validation errors:', result.errors); // Automatically focus the first field with an error focusOnFirstError(formState); } }); ``` ### Remove Extra Data Not in Schema Strip fields from form data that aren't defined in the schema. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { omitExtraData } from '@sjsf/form/omit-extra-data'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { username: { type: 'string' }, email: { type: 'string' } }, additionalProperties: false }; const validator = createFormValidator(); // User data might have extra fields from other sources const rawUserData = { username: 'john_doe', email: 'john@example.com', internalId: 12345, metadata: { source: 'import' } }; // Remove extra fields not defined in schema const cleanData = omitExtraData(validator, schema, rawUserData); console.log(cleanData); // { username: 'john_doe', email: 'john@example.com' } const form = createForm({ ...defaults, schema, initialValue: cleanData, onSubmit: console.log }); ``` ### Multi-Theme Support with daisyUI Use alternative themes like daisyUI for different UI frameworks. ```typescript import { createForm } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/daisyui5-theme'; import { DaisyUIForm } from '@sjsf/daisyui5-theme/components'; const schema: Schema = { type: 'object', properties: { name: { type: 'string', title: 'Full Name' }, role: { type: 'string', enum: ['developer', 'designer', 'manager'], title: 'Role' } } }; const form = createForm({ theme, schema, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log }); ``` ```svelte ``` ### Reactive Form State Access Access and react to form state changes in your application. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { quantity: { type: 'integer', minimum: 1, title: 'Quantity' }, price: { type: 'number', minimum: 0, title: 'Price per unit' } } }; const form = createForm({ ...defaults, schema, initialValue: { quantity: 1, price: 10 }, onSubmit: console.log }); // Access reactive state $effect(() => { console.log('Form changed:', form.isChanged); console.log('Form submitted:', form.isSubmitted); console.log('Submission state:', form.submission.state); console.log('Validation state:', form.fieldsValidation.state); }); // Calculate derived values $derived { const total = (form.value?.quantity ?? 0) * (form.value?.price ?? 0); console.log('Total:', total); } ``` ### Performance Optimization with validateByRetrievedSchema Reduce irrelevant validation errors by validating against a trimmed schema. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import * as defaults from './form-defaults'; const schema: Schema = { type: 'object', properties: { name: { type: 'string', title: 'Name' }, email: { type: 'string', format: 'email', title: 'Email' } }, dependencies: { email: ['name'] }, required: ['name'] }; const form = createForm({ ...defaults, schema, // Enable validation by retrieved schema to reduce irrelevant errors // Note: This makes schema reference unstable, affecting WeakMap caching validateByRetrievedSchema: true, onSubmit: console.log }); ``` ### Caching Utilities for Validator Performance Use built-in caching utilities to optimize validator performance. ```typescript import { createForm, BasicForm } from '@sjsf/form'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { LRUCache, HashedKeyCache } from '@sjsf/form/lib/cache'; import * as defaults from './form-defaults'; // Create LRU cache for validator results const cache = new LRUCache({ maxSize: 100 // Keep last 100 validation results }); // Or use hashed key cache for schema-based caching const hashedCache = new HashedKeyCache({ getHash: (schema) => JSON.stringify(schema) }); const schema: Schema = { type: 'object', properties: { username: { type: 'string', minLength: 3 }, email: { type: 'string', format: 'email' } } }; const form = createForm({ ...defaults, schema, validator: createFormValidator, onSubmit: console.log }); ``` ## Use Cases and Integration The primary use case for svelte-jsonschema-form is rapid form development where schema-driven UI generation eliminates boilerplate code. It excels in admin panels, configuration interfaces, API documentation sites with interactive examples, content management systems, and any application requiring dynamic form generation from backend schemas. The library supports progressive enhancement through SvelteKit integration, allowing forms to work without JavaScript while providing enhanced interactivity when available. Multiple validator backends enable integration with existing validation infrastructure, whether using Ajv for JSON Schema compliance, Zod for TypeScript-first validation, or other schema libraries. Integration patterns include standalone client-side forms with the BasicForm component, server-side rendering with SvelteKit form actions for SEO and no-JS scenarios, embedded forms in existing applications through custom theme integration, and API-driven forms where schemas are fetched from backends. The library's architecture supports deep customization through custom widgets, field templates, theme creation, validator adapters, and field actions for value transformations. Advanced patterns include multi-step forms with schema splitting, conditional field rendering based on discriminators, real-time validation with debouncing, file upload handling with preview components, nullable schema support for optional values, and internationalization through translation system integration. The modular package structure allows importing only needed components and validators, optimizing bundle size for production deployments. Performance optimizations include LRU caching for validation results, keyed array rendering for efficient list updates, and optional validation by retrieved schema to minimize unnecessary error messages.