Skip to main content
This page provides implementation guidance for rendering dynamic forms in your application.

Getting started

To render a dynamic form, you need:
  1. A schema — the JSON Schema defining data types and validation
  2. A uiSchema — the UI Schema defining layout and controls
  3. A data — (optional) existing data to prefill the form
  4. A renderer — either a JSON Forms library or your own implementation

Using JSON Forms libraries

The fastest way to get started is using an official JSON Forms renderer: These libraries handle schema interpretation, validation, and rendering out of the box.

Custom renderers for Enterprise API Suite

You may need to implement custom renderers for options specific to the Enterprise API Suite:
OptionImplementation
dataSource: "countries"Fetch country list from List Countries endpoint
excludeFilter the data source based on the specified restriction

Building a custom renderer

If you need full control over the user experience, you can build your own form renderer. Your implementation must:
  1. Parse the schema — Extract property definitions, types, and validation rules
  2. Parse the uiSchema — Build the layout tree from elements
  3. Render controls — Map schema types to appropriate input components
  4. Apply rules — Evaluate conditions and show/hide/enable/disable elements
  5. Validate input — Enforce schema constraints before submission
  6. Handle progressive disclosure — Re-render when the API returns updated schemas

Handling progressive disclosure

When implementing progressive disclosure:
  1. Render the form from schema, uiSchema, and any existing data
  2. Collect user input for the current step (Category)
  3. Submit the answers to the API
  4. Compare the new response with the previous one:
    • If there’s a new root property in the schema, or a new Category in the uiSchema, re-render the form to show the new questions
    • If the schema and uiSchema are unchanged, the form is complete

Example: React custom renderer for dataSource

Here’s an example of a custom renderer that handles the dataSource option:
import { withJsonFormsControlProps } from '@jsonforms/react';
import { rankWith } from '@jsonforms/core';
import { Autocomplete, TextField } from '@mui/material';
import { useEffect, useState } from 'react';

// Custom renderer that fetches data for dataSource option
const DataSourceControl = (props) => {
  const { data, path, handleChange, uischema, label } = props;
  const [options, setOptions] = useState([]);
  const { dataSource, exclude } = uischema.options || {};

  useEffect(() => {
    if (dataSource === 'countries') {
      // Fetch countries and apply exclude filters
      fetch('https://api.enterprise.uphold.com/core/countries', {
        headers: {
          'Authorization': `Bearer ${accessToken}` // Your OAuth2 access token
        }
      })
        .then((res) => res.json())
        .then((countries) => {
          // Apply exclude filter if specified
          const filtered = exclude?.restrictions
            ? countries.filter((country) =>
                !exclude.restrictions.some((r) =>
                  country.restrictions?.some((cr) => cr.scope === r.scope)
                )
              )
            : countries;
          setOptions(filtered.map((c) => ({ value: c.code, label: c.name })));
        });
    }
  }, [dataSource, exclude]);

  return (
    <Autocomplete
      options={options}
      value={options.find((o) => o.value === data) || null}
      onChange={(_, selected) => handleChange(path, selected?.value)}
      getOptionLabel={(option) => option.label}
      renderInput={(params) => <TextField {...params} label={label} />}
    />
  );
};

// Tester: use this renderer when dataSource is present
const dataSourceTester = rankWith(10, (uischema) =>
  uischema.options?.dataSource ? true : false
);

// Export for use with JsonForms
export const dataSourceRenderer = {
  tester: dataSourceTester,
  renderer: withJsonFormsControlProps(DataSourceControl),
};
Then register the custom renderer:
import { JsonForms } from '@jsonforms/react';
import { materialRenderers, materialCells } from '@jsonforms/material-renderers';
import { dataSourceRenderer } from './DataSourceControl';

const DynamicForm = ({ schema, uiSchema, data }) => (
  <JsonForms
    schema={schema}
    uischema={uiSchema}
    data={data}
    renderers={[dataSourceRenderer, ...materialRenderers]}
    cells={materialCells}
  />
);
This example demonstrates:
  • Detecting the dataSource option with a custom tester
  • Fetching options from the API and applying exclude filters
  • Using Material UI’s Autocomplete for consistent styling
  • Passing existing data to prefill the form

See it in action

To see dynamic forms in practice, explore the KYC processes that use them: