Formily JSON Schema Guide

https://react.formilyjs.org/api/shared/schema

In Formily, JSON Schema is a powerful declarative way to define form structures.
Through JSON Schema, we can describe form fields, validation rules, UI components, etc.
using pure data, enabling dynamic form rendering and configuration management.

What is JSON Schema?

JSON Schema is a specification for validating JSON data structures. In Formily, JSON Schema is extended to describe complete form structures, including:

  • Field types and properties
  • Validation rules
  • UI component configuration
  • Layout information
  • Linkage logic

ISchema Interface Type Details

ISchema is the core interface type for defining JSON Schema in Formily. Let’s understand its structure in detail:

Basic Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface ISchema {
// Field type
type?: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'void'

// Field title
title?: string

// Field description
description?: string

// Default value
default?: any

// Read-only
readOnly?: boolean

// Write-only
writeOnly?: boolean

// Enum values
enum?: any[]

// Constant value
const?: any
}

String Type Properties

1
2
3
4
5
6
7
8
9
10
11
interface ISchema {
// String length constraints
minLength?: number
maxLength?: number

// Regular expression pattern
pattern?: string

// Format validation
format?: 'email' | 'uri' | 'date' | 'time' | 'datetime' | 'ipv4' | 'ipv6' | string
}

Number Type Properties

1
2
3
4
5
6
7
8
9
10
interface ISchema {
// Numeric range
minimum?: number
maximum?: number
exclusiveMinimum?: number
exclusiveMaximum?: number

// Multiple validation
multipleOf?: number
}

Array Type Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ISchema {
// Schema for array items
items?: ISchema | ISchema[]

// Schema for additional items
additionalItems?: ISchema

// Array length constraints
minItems?: number
maxItems?: number

// Uniqueness
uniqueItems?: boolean

// Contains validation
contains?: ISchema
}

Object Type Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface ISchema {
// Property definitions
properties?: Record<string, ISchema>

// Required fields
required?: string[]

// Additional properties
additionalProperties?: boolean | ISchema

// Property count constraints
minProperties?: number
maxProperties?: number

// Property name patterns
patternProperties?: Record<string, ISchema>

// Property dependencies
dependencies?: Record<string, ISchema | string[]>

// Property names
propertyNames?: ISchema
}

Formily Extended Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface ISchema {
// Field name
name?: string

// Field path
path?: string

// UI component
'x-component'?: string

// Component properties
'x-component-props'?: Record<string, any>

// Decorator component
'x-decorator'?: string

// Decorator properties
'x-decorator-props'?: Record<string, any>

// Validator
'x-validator'?: any

// Reactions
'x-reactions'?: any

// Display state
'x-display'?: 'visible' | 'hidden' | 'none'

// Pattern state
'x-pattern'?: 'editable' | 'disabled' | 'readOnly' | 'readPretty'

// Content
'x-content'?: any

// Index
'x-index'?: number
}

JSON Schema Basic Examples

1. Simple Form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const schema: ISchema = {
type: 'object',
properties: {
username: {
type: 'string',
title: 'Username',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'Please enter username'
}
},
email: {
type: 'string',
title: 'Email',
format: 'email',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'Please enter email address'
}
},
age: {
type: 'number',
title: 'Age',
minimum: 0,
maximum: 120,
'x-decorator': 'FormItem',
'x-component': 'NumberPicker'
}
}
}

2. Complex Nested Form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
const complexSchema: ISchema = {
type: 'object',
properties: {
// Basic information
basicInfo: {
type: 'object',
title: 'Basic Information',
'x-decorator': 'FormItem',
'x-component': 'Card',
properties: {
name: {
type: 'string',
title: 'Name',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input'
},
gender: {
type: 'string',
title: 'Gender',
enum: ['male', 'female'],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
options: [
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' }
]
}
}
}
},

// Contact information
contact: {
type: 'object',
title: 'Contact Information',
'x-decorator': 'FormItem',
'x-component': 'Card',
properties: {
phone: {
type: 'string',
title: 'Phone Number',
pattern: '^1[3-9]\\d{9}$',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'Please enter 11-digit phone number'
}
},
address: {
type: 'object',
title: 'Address',
properties: {
province: {
type: 'string',
title: 'Province',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
placeholder: 'Please select province'
}
},
city: {
type: 'string',
title: 'City',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
placeholder: 'Please select city'
}
}
}
}
}
},

// Skills list (array)
skills: {
type: 'array',
title: 'Skills List',
'x-decorator': 'FormItem',
'x-component': 'ArrayCards',
items: {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Skill Name',
'x-decorator': 'FormItem',
'x-component': 'Input'
},
level: {
type: 'string',
title: 'Proficiency Level',
enum: ['beginner', 'intermediate', 'advanced', 'expert'],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
options: [
{ label: 'Beginner', value: 'beginner' },
{ label: 'Intermediate', value: 'intermediate' },
{ label: 'Advanced', value: 'advanced' },
{ label: 'Expert', value: 'expert' }
]
}
}
}
}
}
}
}

3. Form with Validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const validationSchema: ISchema = {
type: 'object',
properties: {
password: {
type: 'string',
title: 'Password',
minLength: 8,
'x-decorator': 'FormItem',
'x-component': 'Password',
'x-validator': [
{
required: true,
message: 'Password cannot be empty'
},
{
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/,
message: 'Password must contain uppercase, lowercase letters and numbers, at least 8 characters'
}
]
},
confirmPassword: {
type: 'string',
title: 'Confirm Password',
'x-decorator': 'FormItem',
'x-component': 'Password',
'x-validator': [
{
required: true,
message: 'Please confirm password'
},
{
validator: (value: string, rule: any, ctx: any) => {
if (value !== ctx.form.values.password) {
return 'Password confirmation does not match'
}
}
}
]
}
}
}

Dynamic Form Examples

1. Conditional Display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const conditionalSchema: ISchema = {
type: 'object',
properties: {
userType: {
type: 'string',
title: 'User Type',
enum: ['individual', 'company'],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
options: [
{ label: 'Individual User', value: 'individual' },
{ label: 'Company User', value: 'company' }
]
}
},

// Individual user information
personalInfo: {
type: 'object',
title: 'Personal Information',
'x-decorator': 'FormItem',
'x-component': 'Card',
'x-reactions': {
dependencies: ['userType'],
fulfill: {
state: {
visible: '{{$deps[0] === "individual"}}'
}
}
},
properties: {
idCard: {
type: 'string',
title: 'ID Card Number',
pattern: '^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
}
},

// Company user information
companyInfo: {
type: 'object',
title: 'Company Information',
'x-decorator': 'FormItem',
'x-component': 'Card',
'x-reactions': {
dependencies: ['userType'],
fulfill: {
state: {
visible: '{{$deps[0] === "company"}}'
}
}
},
properties: {
companyName: {
type: 'string',
title: 'Company Name',
'x-decorator': 'FormItem',
'x-component': 'Input'
},
businessLicense: {
type: 'string',
title: 'Business License Number',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
}
}
}
}

2. Linkage Updates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const linkageSchema: ISchema = {
type: 'object',
properties: {
category: {
type: 'string',
title: 'Product Category',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
options: [
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' },
{ label: 'Food', value: 'food' }
]
}
},

subcategory: {
type: 'string',
title: 'Subcategory',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': {
dependencies: ['category'],
fulfill: {
state: {
dataSource: `{{
$deps[0] === 'electronics' ? [
{ label: 'Phone', value: 'phone' },
{ label: 'Computer', value: 'computer' }
] : $deps[0] === 'clothing' ? [
{ label: 'Top', value: 'top' },
{ label: 'Pants', value: 'pants' }
] : $deps[0] === 'food' ? [
{ label: 'Fruit', value: 'fruit' },
{ label: 'Vegetable', value: 'vegetable' }
] : []
}}`
}
}
}
},

// Display different attribute fields based on category
attributes: {
type: 'object',
'x-reactions': {
dependencies: ['category'],
fulfill: {
schema: {
properties: `{{
$deps[0] === 'electronics' ? {
brand: {
type: 'string',
title: 'Brand',
'x-decorator': 'FormItem',
'x-component': 'Input'
},
model: {
type: 'string',
title: 'Model',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
} : $deps[0] === 'clothing' ? {
size: {
type: 'string',
title: 'Size',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
options: [
{ label: 'S', value: 'S' },
{ label: 'M', value: 'M' },
{ label: 'L', value: 'L' },
{ label: 'XL', value: 'XL' }
]
}
},
color: {
type: 'string',
title: 'Color',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
} : {}
}}`
}
}
}
}
}
}

Rendering Forms with JSON Schema

1. Basic Rendering

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { FormItem, FormLayout, Input, Select, Card } from '@formily/antd'

const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Select,
Card,
},
})

const JsonSchemaForm = () => {
const form = createForm()

return (
<FormProvider form={form}>
<FormLayout layout="vertical">
<SchemaField schema={schema} />
</FormLayout>
</FormProvider>
)
}

2. Dynamic Schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { useState } from 'react'
import { Button } from 'antd'

const DynamicSchemaForm = () => {
const [schema, setSchema] = useState<ISchema>(basicSchema)
const form = createForm()

const addField = () => {
const newSchema = {
...schema,
properties: {
...schema.properties,
[`field_${Date.now()}`]: {
type: 'string',
title: 'Dynamic Field',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
}
}
setSchema(newSchema)
}

return (
<div>
<Button onClick={addField} style={{ marginBottom: 16 }}>
Add Field
</Button>

<FormProvider form={form}>
<FormLayout layout="vertical">
<SchemaField schema={schema} />
</FormLayout>
</FormProvider>
</div>
)
}

Advanced Features

1. Custom Components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Custom component
const CustomComponent = (props: any) => {
return (
<div style={{ border: '1px solid #ccc', padding: 16 }}>
<h4>{props.title}</h4>
<div>{props.children}</div>
</div>
)
}

// Use in Schema
const customSchema: ISchema = {
type: 'object',
properties: {
customField: {
type: 'void',
title: 'Custom Area',
'x-component': 'CustomComponent',
'x-component-props': {
title: 'This is a custom component'
},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input'
}
}
}
}
}

// Register component
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
CustomComponent,
},
})

2. Schema Transformation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Transform simple configuration to complete Schema
const transformConfig = (config: any): ISchema => {
const properties: Record<string, ISchema> = {}

config.fields.forEach((field: any) => {
properties[field.name] = {
type: field.type,
title: field.label,
required: field.required,
'x-decorator': 'FormItem',
'x-component': field.component,
'x-component-props': field.props || {}
}
})

return {
type: 'object',
properties
}
}

// Usage example
const simpleConfig = {
fields: [
{
name: 'username',
label: 'Username',
type: 'string',
component: 'Input',
required: true
},
{
name: 'email',
label: 'Email',
type: 'string',
component: 'Input',
props: { placeholder: 'Please enter email' }
}
]
}

const schema = transformConfig(simpleConfig)

Best Practices

1. Schema Organization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Organize Schema by modules
const userSchemas = {
basic: {
type: 'object',
properties: {
name: { /* ... */ },
email: { /* ... */ }
}
},

contact: {
type: 'object',
properties: {
phone: { /* ... */ },
address: { /* ... */ }
}
}
}

// Merge Schemas
const mergeSchemas = (...schemas: ISchema[]): ISchema => {
return schemas.reduce((merged, schema) => ({
...merged,
properties: {
...merged.properties,
...schema.properties
}
}), { type: 'object', properties: {} })
}

const fullSchema = mergeSchemas(userSchemas.basic, userSchemas.contact)

2. Type Safety

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Define strongly typed Schema
interface UserFormSchema extends ISchema {
properties: {
username: ISchema
email: ISchema
age: ISchema
}
}

const typedSchema: UserFormSchema = {
type: 'object',
properties: {
username: {
type: 'string',
title: 'Username',
'x-decorator': 'FormItem',
'x-component': 'Input'
},
email: {
type: 'string',
title: 'Email',
format: 'email',
'x-decorator': 'FormItem',
'x-component': 'Input'
},
age: {
type: 'number',
title: 'Age',
'x-decorator': 'FormItem',
'x-component': 'NumberPicker'
}
}
}

3. Schema Validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Schema validation utility
const validateSchema = (schema: ISchema): boolean => {
if (!schema.type) {
console.error('Schema must have a type')
return false
}

if (schema.type === 'object' && !schema.properties) {
console.error('Object schema must have properties')
return false
}

// More validation logic...
return true
}

Summary

JSON Schema in Formily provides powerful form configuration capabilities:

  1. Declarative Definition: Describe form structure with data
  2. Type Safety: TypeScript support and ISchema interface
  3. Dynamic Rendering: Support conditional display and linkage updates
  4. Extensibility: Custom components and validation rules
  5. Maintainability: Modular organization and reusability

By properly using JSON Schema, you can build flexible and maintainable dynamic form systems, greatly improving development efficiency and user experience.

Reference Resources


Formily JSON Schema Guide
https://www.hardyhu.cn/2025/06/27/Formily-JSON-Schema-Guide/
Author
John Doe
Posted on
June 27, 2025
Licensed under