@mackehansson/schemaform

Renderable Subset

Understand what Schemaform can render, how to check imported schemas, and how validation stays broader than rendering.

JSON Schema is a validation language. Some constructs describe valid data but do not have a clear form UI: not, arbitrary allOf merges, patternProperties, conditional branches, and $ref graphs are useful to validators but ambiguous for a Form Renderer.

Schemaform therefore promises rendering for a documented Renderable Subset. The Form Builder only emits schemas inside that subset, so builder-created forms render. Imported hand-written schemas should run through the Renderability Check before you attach a UI Schema.

v1 subset

The v1 Renderable Subset is intentionally practical:

JSON Schema constructRendered as
type: "object" with propertiesA form data object
type: "string"Text input by default
format: "email"Email input and email validation
format: "date"Date widget when the renderer provides one
type: "number" / type: "integer"Numeric input
type: "boolean"Checkbox or switch-style widget
enumSelect-style widget
type: "array" with object or scalar itemsRepeater when paired with a UI Schema Array node
required, minLength, maxLength, minimum, maximum, minItems, maxItemsValidation constraints

The UI Schema still decides presentation: Pages, Groups, Columns, Controls, Array row layouts, widgets, navigation mode, and error reveal timing.

Unsupported for rendering

The Renderability Check flags constructs outside the v1 subset, including:

  • $ref
  • allOf, anyOf, oneOf
  • not
  • if, then, else
  • patternProperties
  • dependentRequired, dependentSchemas
  • contains
  • prefixItems
  • unevaluatedProperties, unevaluatedItems
  • polymorphic type arrays such as ["string", "null"]

Validation remains broader than rendering. Ajv can validate full JSON Schema 2020-12, even when a schema includes constructs the Form Renderer cannot turn into Controls.

Check renderability

Use checkRenderability from Core when accepting an imported schema.

import { checkRenderability, type JsonSchema } from "@mackehansson/schemaform";

const importedSchema = {
  type: "object",
  properties: {
    email: {
      type: "string",
      format: "email",
    },
    metadata: {
      patternProperties: {
        "^x-": { type: "string" },
      },
    },
  },
} satisfies JsonSchema;

const issues = checkRenderability(importedSchema);

if (issues.length > 0) {
  console.table(
    issues.map((issue) => ({
      pointer: issue.pointer,
      keyword: issue.keyword,
      message: issue.message,
    })),
  );
}

Each issue includes a JSON Pointer to the unsupported construct. Use that pointer to show actionable import feedback instead of a vague "schema not supported" message.

Importing an existing JSON Schema

An import flow usually has three steps:

import {
  checkRenderability,
  type FormDefinition,
  type JsonSchema,
} from "@mackehansson/schemaform";

export function importJsonSchema(schema: JsonSchema): FormDefinition {
  const issues = checkRenderability(schema);

  if (issues.length > 0) {
    throw new Error(
      issues.map((issue) => `${issue.pointer}: ${issue.message}`).join("\n"),
    );
  }

  return {
    formatVersion: 1,
    schema,
    uiSchema: {
      pages: [
        {
          type: "Page",
          title: "Imported form",
          children: Object.keys(schema.properties ?? {}).map((name) => ({
            type: "Control",
            scope: `#/properties/${name}`,
          })),
        },
      ],
    },
    rules: [],
  };
}

That generated UI Schema is only a starting point. A real importer should escape JSON Pointer segments, choose widgets from formats and enums, and let a developer or admin refine Pages, Groups, Columns, and Rules in the Form Builder.

Validation after import

Do not use the Renderability Check as your data validator. It answers "can Schemaform render this schema as a form?" The Validator answers "does this Working State satisfy the JSON Schema?"

import { validate } from "@mackehansson/schemaform";

const errors = validate(importedSchema, currentWorkingState);

Keeping those questions separate lets Schemaform render a practical subset while still using JSON Schema 2020-12 as the data-and-validation layer.

On this page