json-schemaformsreactjson

Dynamic Forms from JSON Schema: react-jsonschema-form & Beyond

·9 min read·JSON Schema

Writing one schema instead of one form

Every form you build by hand — labels, inputs, validation messages, conditional fields — is code that has to be kept in sync with whatever data shape it's collecting. Schema-driven forms flip this: you write a single JSON Schema describing the data you need, and a library (react-jsonschema-form, JSON Forms, or a custom renderer) generates the entire form UI from it — inputs, labels, validation, and error messages all derived from one source of truth instead of hand-maintained twice.

From schema to rendered form

json
{
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": { "type": "string", "title": "Full Name", "minLength": 2 },
    "email": { "type": "string", "title": "Email", "format": "email" },
    "role": { "type": "string", "title": "Role", "enum": ["admin", "editor", "viewer"] },
    "newsletter": { "type": "boolean", "title": "Subscribe to updates", "default": true }
  }
}

Handed to react-jsonschema-form, this single object produces a text input for name (with a "required" indicator and a min-length validation message), an email-type input for email, a dropdown for role populated from the enum, and a pre-checked checkbox for newsletter — all without writing a single <input> tag:

jsx
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

function SignupForm() {
  return (
    <Form
      schema={schema}
      validator={validator}
      onSubmit={({ formData }) => console.log(formData)}
    />
  );
}

How JSON Schema keywords map to form controls

The mapping from schema to widget is mostly mechanical, which is exactly what makes it automatable:

SchemaRendered as
"type": "string"Text input
"type": "string", "format": "email"Email input (with browser-native validation)
"type": "string", "enum": [...]Select dropdown
"type": "boolean"Checkbox
"type": "integer" / "number"Number input
"type": "array", "items": {...}Repeatable field group with add/remove buttons
"type": "object" (nested)A fieldset grouping its own sub-properties

minLength, maximum, pattern, and required all translate directly into both the input's constraints (a maxlength HTML attribute) and the validation error message shown on submit — the same schema constraints covered in JSONKit's JSON Schema Validator become user-facing form validation, for free.

The uiSchema: styling without touching the data schema

The JSON Schema describes *what data* to collect, deliberately with no opinion on layout or widget choice — that separation is the whole point, since the same data schema might need to render differently in a compact mobile form versus a detailed admin panel. A separate uiSchema object carries presentation hints without polluting the data schema:

json
{
  "email": { "ui:widget": "email", "ui:placeholder": "you@example.com" },
  "role": { "ui:widget": "radio" },
  "ui:order": ["email", "name", "role", "newsletter"]
}

This is why a schema-driven form scales across a large app: the *data contract* (JSON Schema) stays stable and shared, while individual teams or pages layer their own uiSchema presentation on top without forking the underlying data model.

Conditional fields with if/then/else

Real forms need fields that appear only when relevant — a "company name" field only for business accounts, a "shipping address" only when "same as billing" is unchecked. JSON Schema's own if/then/else (covered in depth in JSONKit's JSON Schema Tutorial) expresses this directly in the schema, and form libraries render it as a genuinely dynamic, appearing/disappearing field:

json
{
  "type": "object",
  "properties": {
    "accountType": { "type": "string", "enum": ["personal", "business"] }
  },
  "if": { "properties": { "accountType": { "const": "business" } } },
  "then": { "properties": { "companyName": { "type": "string" } }, "required": ["companyName"] }
}

Selecting "business" makes a required companyName field appear in the rendered form in real time — the conditional logic lives entirely in the schema, not in component state or imperative show/hide code.

Custom widgets for anything the defaults can't express

Not every field maps cleanly to a stock HTML input — a color picker, a rich text editor, a file upload with preview. Every schema-form library supports registering custom widgets, keyed by a ui:widget name or a JSON Schema format value, so the automatic-generation benefit doesn't disappear the moment you need something bespoke:

jsx
const widgets = { colorPicker: MyColorPickerComponent };

<Form schema={schema} uiSchema={{ theme: { "ui:widget": "colorPicker" } }} widgets={widgets} />

Where this pattern earns its complexity

  • Admin panels and internal tools — dozens of similarly-shaped CRUD forms, where hand-coding each one is pure repetition.
  • No-code / low-code platforms — end users define a data shape; the platform must render *some* form without a developer writing UI code per shape.
  • Multi-tenant SaaS with per-customer custom fields — the schema itself (and thus the form) can differ per tenant, driven entirely by data rather than deployed code.
  • API-first products — the same JSON Schema validating a request body server-side can drive the client form that produces that request body, keeping client and server validation from drifting apart.

It's usually overkill for a handful of simple, one-off forms — the schema-authoring overhead only pays for itself once you have enough forms, or enough schema churn, that hand-coding each one becomes the more expensive path.

Building and testing the underlying schema

  1. Generate a starting schema from sample data. Paste a representative form submission into JSONKit's JSON Schema Generator to infer types and required fields as a first draft.
  2. Validate real submissions against it. Use the JSON Schema Validator to confirm actual form output matches what the schema — and therefore the rendered form — was supposed to produce.
  3. Review the schema like a UI spec, since in this pattern, it functionally is one — a reviewer reading the JSON Schema should be able to picture the resulting form.

Frequently asked questions

Client-side, using the same JSON Schema — react-jsonschema-form and JSON Forms both compile the schema into validation logic (typically via Ajv) that runs in the browser. You should still separately validate on the server, since client-side validation is a UX convenience, not a security boundary.

Yes — JSON Forms (Eclipse) supports React, Angular, and Vue; other implementations exist across most major frontend frameworks, since the underlying idea (schema in, form out) isn't framework-specific.

Use a separate uiSchema (react-jsonschema-form) or UISchema (JSON Forms) object — it carries presentation hints like field order, widget choice, and grouping, entirely separate from the data schema that defines validation.

Related but distinct — a form builder is typically a UI for *creating* the JSON Schema (and uiSchema) via drag-and-drop, which is then handed to a renderer library like the ones described here to actually display the form.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.