# 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.