How to Use JSON Schema to Define Form Structures in Formily: A Complete Guide

You can define complex, reactive forms in Formily by instantiating the Schema class with a JSON Schema object that includes standard keywords and Formily-specific extensions like x-component and x-reactions, then rendering it via createSchemaField.

Formily uses a declarative Schema Tree architecture that transforms standard JSON Schema definitions into fully functional form UIs. According to the alibaba/formily source code, the core @formily/json-schema package parses these definitions and converts them into reactive field models that power both React and Vue applications.

The Schema Architecture

Formily's JSON Schema implementation centers on the Schema class located in packages/json-schema/src/schema.ts. This class wraps plain JSON Schema objects and provides methods for traversal, mutation, and conversion to field props.

The Schema constructor accepts a standard JSON Schema object and automatically builds a tree structure with parent-child relationships:

// From packages/json-schema/src/schema.ts#L27-L38
const schema = new Schema({
  type: 'object',
  properties: {
    name: { type: 'string', title: 'Name' }
  }
})

Key methods include toFieldProps() (lines 61-66) which transforms schema nodes into IFieldFactoryProps consumable by Formily's field system, and compile() (lines 72-84) which evaluates embedded {{expression}} strings against a provided scope. The ISchema type definitions in packages/json-schema/src/types.ts specify the complete structure for field descriptions, including type definitions for properties, items, and extension properties.

Defining Your First JSON Schema Form

To create a form structure, you write a JSON Schema using standard keywords plus Formily's x-* extension properties, instantiate the Schema class, and pass it to your framework-specific renderer.

Step 1: Create the Schema Definition

Define your fields using JSON Schema syntax. Standard keywords like type, title, default, enum, and required work as expected, while Formily extensions control UI rendering and behavior:

const jsonSchema = {
  type: 'object',
  properties: {
    username: {
      type: 'string',
      title: 'User Name',
      'x-component': 'Input',
      'x-decorator': 'FormItem',
      'x-validator': [
        { required: true, message: 'Required' },
        { pattern: /^[a-zA-Z0-9_]+$/, message: 'Alphanumeric only' }
      ]
    },
    age: {
      type: 'number',
      title: 'Age',
      'x-component': 'InputNumber',
      'x-decorator': 'FormItem'
    }
  }
}

Step 2: Instantiate and Compile

Import the Schema class from your UI package and instantiate it with your definition. Call compile() to resolve any template expressions:

import { Schema } from '@formily/react' // or @formily/vue

const schema = new Schema(jsonSchema)
schema.compile({ $form: form, $values: form.values })

Step 3: Render with SchemaField

Use createSchemaField to generate a component that renders your schema tree:

import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Input, FormItem } from '@formily/antd'

const form = createForm()
const { SchemaField } = createSchemaField({
  components: { Input, FormItem }
})

export default () => (
  <FormProvider form={form}>
    <SchemaField schema={schema} />
  </FormProvider>
)

Working with Formily Schema Extensions

Formily extends standard JSON Schema with x-* properties that define UI components, validation, and reactive behaviors. These extensions are processed by the transformFieldProps function (referenced in packages/json-schema/src/schema.ts) to generate field configurations.

UI Components and Decoration

  • x-component: Specifies the input component (e.g., 'Input', 'Select', 'DatePicker')
  • x-decorator: Wraps the field with layout components (e.g., 'FormItem' for labels and error display)
  • x-component-props: Passes props to the component, supporting expression values like "{ 'style.color': '{{ $self.value === 'error' ? 'red' : '' }}' }"

Validation Rules

The x-validator property accepts an array of validation rules that map directly to Formily's validation engine:

'x-validator': [
  { required: true },
  { format: 'email' },
  { validator: (value) => value.length > 5 }
]

Field Reactions and Visibility

Use x-reactions for declarative field linking and x-visible for conditional rendering:

// Simple visibility expression
'x-visible': '{{ $values.username === "admin" }}'

// Complex reactions
'x-reactions': {
  target: 'confirmPassword',
  when: '{{ $self.value }}',
  fulfill: {
    state: { visible: true }
  }
}

Compiling Dynamic Expressions

The Schema class provides compile() and shallowCompile() static methods to evaluate template expressions within your schema. Expressions use the {{...}} syntax and access a scoped context containing $form, $values, $self, and other variables.

As implemented in packages/json-schema/src/schema.ts (lines 72-84), the compilation process traverses the schema tree and evaluates expressions against the provided scope:

// Compile with full scope
schema.compile({ 
  $form: form, 
  $values: form.values,
  $record: form.values
})

// Shallow compile for performance (single level)
Schema.shallowCompile(schema.properties, scope)

This enables dynamic component props, conditional validation rules, and calculated default values without imperative code.

Advanced Schema Patterns

Nested Objects and Arrays

Define complex nested structures using standard JSON Schema properties and items keywords. The Schema constructor recursively creates child nodes via addProperty and setItems methods:

const schema = new Schema({
  type: 'object',
  properties: {
    address: {
      type: 'object',
      properties: {
        city: { type: 'string' },
        zip: { type: 'string' }
      }
    },
    contacts: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          name: { type: 'string' },
          phone: { type: 'string' }
        }
      }
    }
  }
})

Schema References

Reuse schema fragments using $ref and definitions. The findDefinitions() method in packages/json-schema/src/schema.ts resolves references locally without remote loading:

const schema = new Schema({
  definitions: {
    address: {
      type: 'object',
      properties: {
        street: { type: 'string' }
      }
    }
  },
  type: 'object',
  properties: {
    home: { $ref: '#/definitions/address' },
    work: { $ref: '#/definitions/address' }
  }
})

Version Migration with Polyfills

For upgrading from older Formily versions, use Schema.registerPolyfills() to transform legacy schemas:

Schema.registerPolyfills('1.0', (schema) => {
  // Migration logic
})

Summary

  • Write JSON Schema using standard keywords and Formily extensions (x-component, x-decorator, x-validator, x-reactions) to declaratively define form structure and behavior.
  • Instantiate new Schema(json) to build a traversable tree structure that maintains parent-child relationships and enables schema manipulation.
  • Call schema.compile(scope) to evaluate {{expression}} templates against your form instance, enabling dynamic props and conditional logic.
  • Render via createSchemaField by passing the compiled schema to the generated SchemaField component, which internally calls toFieldProps() to map schema nodes to field models.
  • Leverage advanced features like $ref resolution for schema reuse and x-reactions for declarative cross-field dependencies.

Frequently Asked Questions

What is the difference between x-component and x-decorator in Formily JSON Schema?

x-component specifies the actual input control (such as Input, Select, or DatePicker) that collects user data, while x-decorator wraps that component with layout or context providers (such as FormItem for labels, error messages, and grid layout). Both are processed by toFieldProps() in packages/json-schema/src/schema.ts to generate the final field configuration.

How do I make one field visible only when another field has a specific value?

Use the x-visible extension with a template expression referencing $values or $form. For example: 'x-visible': '{{ $values.role === "admin" }}'. Before rendering, call schema.compile({ $values: form.values }) to evaluate the expression against the current form state.

Can I reuse schema definitions across multiple fields?

Yes. Define reusable schemas in a definitions object at the root level, then reference them using $ref (e.g., { $ref: '#/definitions/address' }). Formily's Schema class includes a findDefinitions() method that resolves these references during instantiation, merging the target schema properties into the referencing field.

Where does Formily store the mapping between JSON Schema types and field props?

The transformation logic resides in packages/json-schema/src/schema.ts within the toFieldProps() method (lines 61-66) and associated transformer functions. This code maps standard JSON Schema attributes (type, title, default) and Formily extensions (x-component, x-validator) to the IFieldFactoryProps interface consumed by @formily/core's field creation system.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →