Skip to main content
New to FormEngine? It’s a React library that renders forms from JSON schemas — free, MIT-licensed. Start here →

What you’ll learn

After this tutorial, you’ll understand every part of a FormEngine JSON schema. You’ll know what key, type, props, children, schema, validations, and events do, and why they matter. By the end, you’ll be able to read any FormEngine JSON form and explain how it works to someone else. This is NOT a reference manual — it’s a walkthrough like a teacher explaining a sentence word by word.

The simplest possible form

Let’s start with the absolute minimum: a text input field.
{
  "type": "Screen",
  "children": [
    {
      "key": "email",
      "type": "RsInput",
      "props": {
        "label": { "value": "Email" }
      }
    }
  ]
}
That’s it. One field. Let’s break down every line.

Understanding the form tree: type, key, and children

FormEngine forms are trees, not flat lists. The root is always a "Screen" component, which is the page itself. Everything else is inside its children array.

The root: type: "Screen"

{
  "type": "Screen",
  "children": [ ... ]
}
  • type: "Screen" — This tells FormEngine “I’m defining a page”. Every form must start with Screen as the root.
  • children — An array of child components. Think of it like: “Inside this screen, place these components.”

Child components: type and key

Inside children, each component needs two things:
{
  "key": "email",
  "type": "RsInput",
  ...
}
key — The data identifier The key is the name of the field in your form’s data output. When a user types in this field, FormEngine captures the value under this key.
{
  "key": "email",
  "type": "RsInput"
}
When the user enters “john@example.com” and you call formEngine.getData(), you get:
{
  email: "john@example.com"
}
Important: The key must be unique within the form (no two fields with "key": "email"). The key should be a valid JavaScript variable name (alphanumeric + underscore). type — The component to render The type tells FormEngine which UI component to use. FormEngine supports three families of components:
Component familyNaming conventionExampleComes from
React SuiteStart with RsRsInput, RsDropdown, RsCheckbox@react-form-builder/components-rsuite (free)
Material UIStart with MuiMuiTextField, MuiSelect, MuiCheckbox@react-form-builder/components-material-ui (free)
MantineStart with MtMtTextInput, MtSelect, MtCheckbox, MtRating@react-form-builder/components-mantine (free)
Important: You must install the package that matches your component family. If you installed @react-form-builder/components-rsuite, you can only use Rs* types. You can’t use RsInput with a Mantine view. If FormEngine encounters a type it doesn’t recognize, you’ll see an error in the browser console: Unknown component: UnknownType.

Nesting: children inside components

Components can have children. For example, a RsCard (a card-like container) can wrap multiple fields:
{
  "type": "Screen",
  "children": [
    {
      "type": "RsCard",
      "children": [
        {
          "key": "firstName",
          "type": "RsInput"
        },
        {
          "key": "lastName",
          "type": "RsInput"
        }
      ]
    }
  ]
}
Here, the Screen contains an RsCard, and the RsCard contains two input fields. This creates a visual card around the two fields. Not all components support children. For example, RsInput (a text field) doesn’t have children — it’s a leaf node. But layout components like RsCard, RsContainer, and RsTab do. Check the component documentation to see which components support children.

Component properties: the props object

The props object configures how a component looks and behaves. It maps to the component’s React props.
{
  "key": "email",
  "type": "RsInput",
  "props": {
    "label": { "value": "Email Address" },
    "placeholder": { "value": "user@example.com" },
    "disabled": { "value": false }
  }
}

The { "value": ... } wrapper pattern

You might notice that every prop value is wrapped in { "value": ... }. This looks redundant, but it’s intentional:
// ❌ Don't do this:
"label": "Email Address"

// ✅ Do this:
"label": { "value": "Email Address" }
Why? Because props can be dynamic. Later, when you learn about computed properties and conditional logic, you can replace { "value": ... } with { "computed": "path.to.value" } or { "if": [...] }. The wrapper prepares your schema for these advanced patterns. For now, just remember: every prop value goes inside { "value": ... }.

Common props for input components

Most input components (text, dropdown, checkbox) support these props:
PropTypeExampleWhat it does
labelstring{ "value": "Full Name" }The label displayed above the field
placeholderstring{ "value": "Enter your name" }Placeholder text inside the field
disabledboolean{ "value": false }If true, user can’t interact with the field
sizestring{ "value": "lg" }Size: "sm", "md", "lg" (component-dependent)
requiredboolean{ "value": true }If true, adds a red asterisk (*) to the label
hiddenboolean{ "value": false }If true, the field is hidden (but still in data)
helpTextstring{ "value": "We'll use this to contact you" }Helper text displayed below the field
Different components support different props. For example, RsInput might support maxLength, but RsDropdown doesn’t. Always check the component reference for the full list.

Adding validation: the schema object

Validation tells FormEngine which data is valid and which is not.
{
  "key": "email",
  "type": "RsInput",
  "props": {
    "label": { "value": "Email" }
  },
  "schema": {
    "validations": [
      {
        "key": "required",
        "args": { "message": "Email is required" }
      },
      {
        "key": "email",
        "args": { "message": "Please enter a valid email" }
      }
    ]
  }
}
The schema object contains validation rules in a validations array. Each rule has:
  • key — The rule name (e.g., "required", "email", "minLength")
  • args — Rule-specific arguments, usually including a custom error message

Available validation rules

RuleUsageError message
requiredField must not be empty(default) “This field is required”
emailField must be a valid email(default) “Please enter a valid email”
minNumber must be ≥ valueargs: { "min": 18, "message": "Must be 18 or older" }
maxNumber must be ≤ valueargs: { "max": 100, "message": "Max value is 100" }
minLengthString must be ≥ N charactersargs: { "minLength": 3, "message": "At least 3 characters" }
maxLengthString must be ≤ N charactersargs: { "maxLength": 50, "message": "Max 50 characters" }
regexString must match patternargs: { "pattern": "^[A-Z]", "message": "Must start with capital letter" }
matchValue must match another fieldargs: { "field": "password", "message": "Passwords don't match" }
customCustom validation functionargs: { "fn": "myCustomRule", "message": "Invalid input" }

Custom error messages

By default, each rule has a built-in error message. But you can override it:
{
  "key": "age",
  "type": "RsInput",
  "schema": {
    "validations": [
      {
        "key": "required",
        "args": { "message": "Age is required — we need to know how old you are" }
      }
    ]
  }
}
When validation fails, FormEngine shows your custom message on the field.

Wiring interactivity: the events object

Events let fields respond to user actions. For example, when a user submits a form, clears a field, or selects a dropdown option.
{
  "key": "country",
  "type": "RsDropdown",
  "props": {
    "label": { "value": "Country" }
  },
  "events": {
    "onChange": [
      {
        "name": "resetField",
        "type": "common",
        "args": { "key": "city" }
      }
    ]
  }
}
This means: “When the user changes the country dropdown, reset the city field.”

Common event types

EventTriggeredExample use case
onChangeUser changes the field valueClear dependent fields, calculate total price, update summary
onClickUser clicks a buttonSubmit form, save draft, show modal
onBlurUser leaves the fieldValidate field, load suggestions
onFocusUser enters the fieldShow help text, load options

Action reference format

Inside events, you reference actions by name:
{
  "name": "resetField",    // Action name — must exist in top-level actions object
  "type": "common",        // Type: "common" for built-in actions, "custom" for code
  "args": { "key": "city" } // Arguments for the action
}
See the section below on top-level form settings to learn where actions are defined.

Top-level form settings

Beyond the tree structure, every FormEngine form has top-level configuration:
{
  "type": "Screen",
  "children": [...],
  
  // === Form configuration (optional) ===
  "tooltipType": "icon",      // How tooltips appear: "icon", "text", or "none"
  "errorType": "inline",      // How errors display: "inline" (below field), "tooltip", or "none"
  "modalType": "center",      // Modal position: "center", "full", or custom
  
  // === Localization (optional) ===
  "localization": {
    "languages": {
      "en": { "label": "English" },
      "es": { "label": "Español" }
    },
    "defaultLanguage": "en"
  },
  
  // === Custom actions (optional) ===
  "actions": {
    "loadCountries": {
      "type": "custom",
      "code": "async (data) => { ... }"
    }
  }
}

tooltipType, errorType, modalType

These control how FormEngine displays validation messages and help text:
  • tooltipType: "icon" — Show a small (i) icon; clicking it displays help text
  • errorType: "inline" — Display error messages directly below the field
  • modalType: "center" — Center modals on screen (vs "full" for full-screen)

localization

If your form supports multiple languages, define them here. This is covered in the localization guide.

actions

Define custom actions that fields can trigger:
{
  "actions": {
    "loadCountries": {
      "type": "custom",
      "code": "async (data) => { ... }"
    },
    "resetField": {
      "type": "common"
    }
  }
}

Complete annotated example: a contact form

Let’s put it all together. Here’s a real contact form with every part explained:
{
  // === Root: the screen ===
  "type": "Screen",
  
  // === Settings ===
  "errorType": "inline",
  "tooltipType": "icon",
  
  // === Form structure ===
  "children": [
    // === Container: wrap form fields in a card ===
    {
      "type": "RsCard",
      "props": {
        "header": { "value": "Contact Us" }
      },
      "children": [
        // === Field 1: First Name (simple text input) ===
        {
          "key": "firstName",
          "type": "RsInput",
          "props": {
            "label": { "value": "First Name" },
            "placeholder": { "value": "John" },
            "required": { "value": true }
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "First name is required" }
              },
              {
                "key": "minLength",
                "args": { "minLength": 2, "message": "At least 2 characters" }
              }
            ]
          }
        },
        
        // === Field 2: Email (text input + email validation) ===
        {
          "key": "email",
          "type": "RsInput",
          "props": {
            "label": { "value": "Email Address" },
            "placeholder": { "value": "john@example.com" },
            "type": { "value": "email" },
            "required": { "value": true }
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "Email is required" }
              },
              {
                "key": "email",
                "args": { "message": "Please enter a valid email address" }
              }
            ]
          }
        },
        
        // === Field 3: Subject (text input) ===
        {
          "key": "subject",
          "type": "RsInput",
          "props": {
            "label": { "value": "Subject" },
            "placeholder": { "value": "How can we help?" },
            "required": { "value": true }
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "Subject is required" }
              }
            ]
          }
        },
        
        // === Field 4: Message (multi-line text area) ===
        {
          "key": "message",
          "type": "RsTextArea",
          "props": {
            "label": { "value": "Message" },
            "placeholder": { "value": "Tell us more..." },
            "rows": { "value": 5 },
            "required": { "value": true }
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "Message is required" }
              },
              {
                "key": "minLength",
                "args": { "minLength": 10, "message": "Message must be at least 10 characters" }
              }
            ]
          }
        },
        
        // === Field 5: Dropdown (select from options) ===
        {
          "key": "category",
          "type": "RsDropdown",
          "props": {
            "label": { "value": "Category" },
            "required": { "value": true },
            "options": { "value": [
              { "label": "Sales", "value": "sales" },
              { "label": "Support", "value": "support" },
              { "label": "Partnership", "value": "partnership" }
            ]}
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "Please select a category" }
              }
            ]
          }
        },
        
        // === Field 6: Checkbox (agree to terms) ===
        {
          "key": "agreeToTerms",
          "type": "RsCheckbox",
          "props": {
            "label": { "value": "I agree to the Terms of Service" },
            "required": { "value": true }
          },
          "schema": {
            "validations": [
              {
                "key": "required",
                "args": { "message": "You must agree to the terms" }
              }
            ]
          }
        },
        
        // === Submit button ===
        {
          "type": "RsButton",
          "props": {
            "label": { "value": "Send Message" },
            "type": { "value": "primary" }
          },
          "events": {
            "onClick": [
              {
                "name": "submitForm",
                "type": "common"
              }
            ]
          }
        }
      ]
    }
  ]
}
When a user fills this form and clicks “Send Message”, FormEngine returns:
{
  firstName: "John",
  email: "john@example.com",
  subject: "Question about pricing",
  message: "Hi, I'd like to know more about...",
  category: "sales",
  agreeToTerms: true
}

Where to find available components and their props

Now that you understand the structure, you need to know which components exist and what props they support. Each component page shows:
  • What the component looks like
  • All available props with descriptions
  • Code examples

Common mistakes

Mistake 1: Missing or duplicate key

// ❌ Wrong: no key
{
  "type": "RsInput",
  "props": { "label": { "value": "Email" } }
}

// ❌ Wrong: two fields with same key
{
  "children": [
    { "key": "email", "type": "RsInput" },
    { "key": "email", "type": "RsDropdown" }  // Duplicate!
  ]
}

// ✅ Correct
{
  "key": "email",
  "type": "RsInput",
  "props": { "label": { "value": "Email" } }
}
Result: If a field has no key, FormEngine won’t capture its data. If two fields share the same key, the second one overwrites the first.

Mistake 2: Wrong component type

// ❌ Wrong: typo in component name
{
  "type": "RsInpu"  // Missing 't'
}

// ❌ Wrong: RSuite component with Material UI package
// (You installed @react-form-builder/components-material-ui, but used RsInput)
{
  "type": "RsInput"
}

// ✅ Correct: match the package you installed
{
  "type": "RsInput"  // If you installed @react-form-builder/components-rsuite
}
Result: You’ll see an error in the browser console: Unknown component: RsInpu or Unknown component: RsInput. How to fix: Check the component list for your package, and use the exact name shown.

Mistake 3: Forgetting the { "value": ... } wrapper

// ❌ Wrong: prop value not wrapped
{
  "key": "email",
  "type": "RsInput",
  "props": {
    "label": "Email"  // Should be { "value": "Email" }
  }
}

// ✅ Correct
{
  "key": "email",
  "type": "RsInput",
  "props": {
    "label": { "value": "Email" }
  }
}
Result: The prop might be ignored, or FormEngine might crash. The error message depends on the component, but usually it’s not clear.

Mistake 4: Using the wrong validation rule name

// ❌ Wrong: "isEmail" doesn't exist
{
  "key": "email",
  "type": "RsInput",
  "schema": {
    "validations": [
      {
        "key": "isEmail",  // Should be "email"
        "args": {}
      }
    ]
  }
}

// ✅ Correct
{
  "key": "email",
  "type": "RsInput",
  "schema": {
    "validations": [
      {
        "key": "email",
        "args": { "message": "Please enter a valid email" }
      }
    ]
  }
}
Result: The validation doesn’t run — the field accepts invalid emails. Check the validation guide for the full list of rule names.

Mistake 5: Putting non-leaf components in a field’s children

// ❌ Wrong: RsInput doesn't support children
{
  "key": "email",
  "type": "RsInput",
  "children": [  // RsInput is a leaf node
    { "type": "RsLabel", "props": { "text": { "value": "Help" } } }
  ]
}

// ✅ Correct: wrap the input in a layout component that supports children
{
  "type": "RsCard",
  "children": [
    {
      "key": "email",
      "type": "RsInput",
      "props": { "label": { "value": "Email" } }
    }
  ]
}
Result: FormEngine ignores the children, or crashes if the component doesn’t expect them.

Troubleshooting: when things go wrong

”Unknown component: RsInput” error

Cause: Either the component name is misspelled, or you haven’t installed the correct UI library package. Fix:
  1. Check the exact component name in the component reference
  2. Verify you installed the right package: npm install @react-form-builder/components-rsuite
  3. Import the components in your React code

Form doesn’t render, or renders empty

Cause: Could be several things:
  • Missing "type": "Screen" at the root
  • Typo in a component’s type property
  • Syntax error in the JSON (extra comma, missing bracket)
Fix:
  1. Open the browser’s Developer Console (F12 → Console tab)
  2. Look for red error messages — they usually tell you what’s wrong
  3. Copy the error message and search the docs, or ask in GitHub Issues

Field value doesn’t save (data is empty)

Cause: The field doesn’t have a key property. Fix: Add "key": "fieldName" to every input field.

Validation doesn’t show errors

Cause:
  • The validation rule name is misspelled (use "email", not "isEmail")
  • errorType is set to "none" (so errors aren’t displayed)
  • The field doesn’t have a schema object
Fix:
  1. Check the validation rules list for correct rule names
  2. Set "errorType": "inline" or "tooltip" at the form level
  3. Make sure each field has a schema.validations array

Button or dropdown doesn’t work

Cause: The component is missing an events object with the correct action. Fix: Add an events object to the button or dropdown, and reference the action you want to trigger.

Next steps

Now that you understand JSON structure, here’s where to go next: Or, browse the full Components Library to explore what you can build.
Last modified on April 16, 2026