Backend-Driven Form Rendering with JSON Schema in Formily: Complete Implementation Guide

Backend-driven form rendering with JSON Schema in Formily allows your server to send a declarative payload that the framework automatically converts into a fully functional form UI, enabling you to modify fields, validation rules, and layout without touching frontend code.

The alibaba/formily repository provides a schema-driven form framework that treats forms as pure data structures. By leveraging backend-driven form rendering with JSON Schema in Formily, you create a strict separation between your data layer and presentation layer—the backend defines the form shape, while the frontend simply renders what it receives. This architecture enables rapid iteration through centralized form management and eliminates the need for redeploying client applications when business requirements change.

How Backend-Driven Rendering Works in Formily

Formily implements a four-stage pipeline that transforms a raw JSON Schema into interactive UI components. Understanding this flow is essential for debugging and extending schema-driven forms.

The JSON Schema to Formily Schema Pipeline

When your backend returns a JSON Schema, Formily's @formily/json-schema package immediately begins processing. The transformer located in packages/json-schema/src/transformer.ts traverses the schema tree to normalize the structure and resolve $ref pointers. During this walk, it merges default values and attaches Formily-specific extensions including x-component, x-decorator, and x-reactions to each node. The output is a structured Schema object defined in packages/json-schema/src/schema.ts, which provides instance methods like addProperty and setItems for runtime manipulation.

The Rendering Architecture

After transformation, the frontend creates a Form instance using createForm() from packages/core/src/models/Form.ts. This central model holds form state, field values, validation status, and side effects. The SchemaField component—implemented in packages/vue/src/components/SchemaField.ts for Vue or packages/react/src/components/SchemaField.tsx for React—injects the parsed schema into the component tree via framework-specific context providers. Finally, RecursionField (packages/vue/src/components/RecursionField.ts) recursively walks the schema tree, rendering each leaf node as its designated component (e.g., SchemaStringField) based on the x-component mappings.

Implementation: From Backend to Frontend

Implementing backend-driven forms requires coordination between your API layer and Formily's component registration system. The following steps demonstrate the complete data flow from server to browser.

Step 1: Serving the Schema from Your Backend

Your backend must expose an endpoint that returns a valid JSON Schema extended with Formily properties. These extensions use the x- prefix to specify which UI components should render each field.

// server.ts - Express example
import express from 'express';
const app = express();

app.get('/api/form-schema', (req, res) => {
  res.json({
    type: 'object',
    properties: {
      username: {
        type: 'string',
        title: 'User Name',
        'x-component': 'Input',
        required: true,
      },
      age: {
        type: 'number',
        title: 'Age',
        'x-component': 'NumberPicker',
        minimum: 0,
      },
      agree: {
        type: 'boolean',
        title: 'Agree to Terms',
        'x-component': 'Checkbox',
        'x-decorator': 'FormItem',
      },
    },
  });
});

app.listen(3000);

Step 2: Converting JSON Schema to Internal Schema Objects

When the frontend fetches this payload, Formily automatically processes it through the transformer pipeline. You do not manually instantiate the Schema class; instead, you pass the raw JSON directly to the rendering components, which internally utilize packages/json-schema/src/transformer.ts to handle normalization and extension merging.

Step 3: Rendering with SchemaField Components

The SchemaField component acts as the bridge between your schema data and your registered UI components. You must first register your actual input components (Input, Checkbox, etc.) using createSchemaField() before the schema can render.

Code Examples

Vue 3 Implementation

In Vue applications, you combine createForm() with FormProvider and the schema-enabled SchemaField component. Fetch the schema in your setup function and conditionally render once loaded.

<script setup lang="ts">
import { createForm } from '@formily/core';
import { createSchemaField, FormProvider } from '@formily/vue';
import { onMounted, ref } from 'vue';

const form = createForm();

const { SchemaField } = createSchemaField({
  components: {
    Input: /* your Input component */,
    NumberPicker: /* your NumberPicker component */,
    Checkbox: /* your Checkbox component */,
    FormItem: /* your FormItem decorator */,
  },
});

const schema = ref(null);
onMounted(async () => {
  const resp = await fetch('/api/form-schema');
  schema.value = await resp.json();
});
</script>

<template>
  <FormProvider :form="form">
    <SchemaField v-if="schema" :schema="schema" />
  </FormProvider>
</template>

React Implementation

The React implementation follows an identical pattern, using hooks to fetch the remote schema and FormProvider to inject the form instance context.

import { createForm } from '@formily/core';
import { createSchemaField, FormProvider } from '@formily/react';
import { useEffect, useState } from 'react';

const form = createForm();

const { SchemaField } = createSchemaField({
  components: {
    Input: (props) => <input {...props} />,
    NumberPicker: (props) => <input type="number" {...props} />,
    Checkbox: (props) => <input type="checkbox" {...props} />,
  },
});

export const RemoteForm = () => {
  const [schema, setSchema] = useState<any>(null);

  useEffect(() => {
    fetch('/api/form-schema')
      .then((res) => res.json())
      .then(setSchema);
  }, []);

  return (
    <FormProvider form={form}>
      {schema && <SchemaField schema={schema} />}
    </FormProvider>
  );
};

Key Source Files in the Formily Repository

Understanding the internal architecture helps when debugging complex schema transformations or extending the framework's capabilities:

Summary

  • Backend-driven form rendering moves form definitions from frontend code to server-side data, enabling instant updates without redeployment.
  • The transformer.ts file in @formily/json-schema handles the heavy lifting of converting JSON Schema into Formily's internal Schema objects.
  • SchemaField components in both Vue and React act as the entry point for rendering, while RecursionField manages nested structures.
  • Form extensions like x-component and x-decorator allow you to specify UI behavior directly within your JSON Schema.
  • The Form model (packages/core/src/models/Form.ts) maintains all runtime state independently of the rendering layer.

Frequently Asked Questions

What is the role of the transformer.ts file in Formily?

The transformer.ts file in packages/json-schema/src/ serves as the core engine that parses incoming JSON Schema payloads. It traverses the schema tree to resolve $ref references, normalize property structures, and merge Formily-specific extensions (like x-component and x-reactions) into a cohesive Schema instance that the rendering engine can consume.

How does Formily handle component registration for schema-driven forms?

Formily requires you to register your UI components explicitly using createSchemaField() before rendering. This function maps string identifiers used in the x-component property (such as "Input" or "Checkbox") to actual Vue or React components. The SchemaField component then looks up these mappings during the recursive rendering process handled by RecursionField.

Can Formily render nested or complex JSON Schema structures?

Yes. Formily's RecursionField component (packages/vue/src/components/RecursionField.ts or React equivalent) automatically handles nested objects and arrays within your JSON Schema. It walks the entire schema tree depth-first, rendering each node according to its type and x-component definition, making it suitable for deeply nested forms without manual iteration.

What happens when the backend updates the JSON Schema?

When the backend modifies the schema—adding fields, changing validation rules, or updating layouts—the frontend automatically reflects these changes on the next fetch request. Because the UI is derived entirely from the schema data, no frontend code changes or redeployment are required. The transformer processes the new structure immediately, and SchemaField renders the updated component tree.

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 →