Skip to content

Component Manifest Reference

Component manifests define the draggable components available in the builder's palette. Each manifest describes a component's type, category, display name, and default node properties.

ComponentManifest Interface

ts
interface ComponentManifest {
  /** Unique manifest ID used to create nodes from palette drags */
  id: string

  /**
   * Display name shown in the palette.
   * Should be short and descriptive (e.g., "Text Field", "Horizontal Layout").
   */
  name: string

  /** Optional longer description for tooltips or documentation */
  description?: string

  /** Icon identifier for display in the palette */
  icon: string

  /**
   * Category for grouping in the palette.
   * Must be one of: "layout", "control", "display".
   */
  category: ManifestCategory

  /**
   * The SchemaNode type this manifest creates.
   * Must be one of: VerticalLayout, HorizontalLayout, Group,
   * Categorization, Category, Control, Label.
   */
  nodeType: SchemaNodeType

  /**
   * Default SchemaNode property overrides applied when
   * a new node is created from this manifest.
   */
  defaults: SchemaNodeDefaults

  /**
   * JSON Schema and UI Schema used to render the property editor
   * panel for nodes created from this manifest.
   */
  propertySchema: {
    jsonSchema: Record<string, unknown>
    uiSchema: Record<string, unknown>
  }

  /**
   * Optional matching criteria used to associate existing JSON Schema
   * properties with this manifest during import.
   */
  schemaMatch?: {
    types?: JsonSchemaType[]
    formats?: string[]
  }

  /**
   * Optional placement constraints for the builder tree.
   */
  placement?: {
    allowedParents?: SchemaNodeType[]
    isContainer?: boolean
    allowedChildren?: SchemaNodeType[]
  }

  /**
   * Optional custom preview renderer for the builder canvas.
   */
  previewRenderer?: { tester: Record<string, unknown>; component: unknown }
}

SchemaNodeDefaults

The defaults object provides initial values for nodes created from this manifest. It is a restricted type with the following fields:

ts
interface SchemaNodeDefaults {
  propertyName?: string
  schemaProperties?: JsonSchemaFragment
  options?: Record<string, unknown>
  label?: string | boolean
}
FieldTypeTypical Usage
propertyNamestringBase name for auto-generated property names
schemaPropertiesJsonSchemaFragmentSet the JSON Schema type, constraints, format
optionsRecord<string, unknown>UI options passed through to the UI Schema
labelstring | booleanDefault label text or auto-label behavior

Example Defaults

ts
// A simple string input
defaults: {
  propertyName: 'text',
  schemaProperties: { type: 'string' },
}

// An email input with validation
defaults: {
  propertyName: 'email',
  schemaProperties: {
    type: 'string',
    format: 'email',
    maxLength: 254,
  },
  label: 'Email Address',
}

// A numeric slider
defaults: {
  propertyName: 'number',
  schemaProperties: {
    type: 'number',
    minimum: 0,
    maximum: 100,
  },
  options: {
    slider: true,
  },
}

ManifestCategory

Categories control how components are grouped in the palette. The palette renders one collapsible group per unique category.

ts
type ManifestCategory = 'layout' | 'control' | 'display'

Built-in categories:

CategoryComponents
layoutVerticalLayout, HorizontalLayout, Group, Categorization, Category
controlString, Number, Integer, Boolean, Date, DateTime, Enum, MultiEnum, Array, Object, OneOf controls
displayLabel

Placement Constraints

Some node types have placement constraints enforced by the builder's validation:

ConstraintRule
CategoryCan only be a direct child of Categorization
CategorizationCan only contain Category children
ControlsCannot have children (leaf nodes)
LabelsCannot have children (leaf nodes)

The builder does not prevent invalid placements during drag-and-drop, but validateSchemaNode() reports placement violations as errors.

Property Schema Structure

When a Control manifest sets schemaProperties, those properties flow into the generated JSON Schema. Here is how each JSON Schema type maps:

String Controls

ts
defaults: {
  propertyName: 'text',
  schemaProperties: {
    type: 'string',
    // Optional constraints
    minLength: 1,
    maxLength: 255,
    pattern: '^[A-Z].*',
    format: 'email',       // or: date, uri, date-time, etc.
    // Optional enum
    enum: ['option1', 'option2'],
  },
}

Number / Integer Controls

ts
defaults: {
  propertyName: 'number',
  schemaProperties: {
    type: 'number',  // or 'integer'
    minimum: 0,
    maximum: 100,
    exclusiveMinimum: 0,
    exclusiveMaximum: 101,
    multipleOf: 0.5,
  },
}

Boolean Controls

ts
defaults: {
  propertyName: 'flag',
  schemaProperties: {
    type: 'boolean',
    default: false,
  },
}

Array Controls

ts
defaults: {
  propertyName: 'items',
  schemaProperties: {
    type: 'array',
    items: { type: 'string' },
    minItems: 1,
    maxItems: 10,
    uniqueItems: true,
  },
}

Object Controls

ts
defaults: {
  propertyName: 'obj',
  schemaProperties: {
    type: 'object',
    properties: {
      street: { type: 'string' },
      city: { type: 'string' },
    },
    required: ['street'],
    additionalProperties: false,
  },
}

Built-in Manifest Examples

vertical-layout

ts
{
  id: 'vertical-layout',
  name: 'Vertical Layout',
  category: 'layout',
  nodeType: 'VerticalLayout',
  defaults: {},
}

Generated UI Schema:

json
{ "type": "VerticalLayout", "elements": [] }

horizontal-layout

ts
{
  id: 'horizontal-layout',
  name: 'Horizontal Layout',
  category: 'layout',
  nodeType: 'HorizontalLayout',
  defaults: {},
}

Generated UI Schema:

json
{ "type": "HorizontalLayout", "elements": [] }

group

ts
{
  id: 'group',
  name: 'Group',
  category: 'layout',
  nodeType: 'Group',
  defaults: { label: 'Group' },
}

Generated UI Schema:

json
{ "type": "Group", "label": "Group", "elements": [] }

categorization

ts
{
  id: 'categorization',
  name: 'Categorization',
  category: 'layout',
  nodeType: 'Categorization',
  defaults: {},
}

Generated UI Schema:

json
{ "type": "Categorization", "elements": [] }

category

ts
{
  id: 'category',
  name: 'Category',
  category: 'layout',
  nodeType: 'Category',
  defaults: { label: 'Category' },
}

Generated UI Schema:

json
{ "type": "Category", "label": "Category", "elements": [] }

string-control

ts
{
  id: 'string-control',
  name: 'Text Field',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'text',
    schemaProperties: { type: 'string' },
  },
}

Generated JSON Schema property:

json
{ "text": { "type": "string" } }

Generated UI Schema element:

json
{ "type": "Control", "scope": "#/properties/text" }

number-control

ts
{
  id: 'number-control',
  name: 'Number Field',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'number',
    schemaProperties: { type: 'number' },
  },
}

integer-control

ts
{
  id: 'integer-control',
  name: 'Integer Field',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'integer',
    schemaProperties: { type: 'integer' },
  },
}

boolean-control

ts
{
  id: 'boolean-control',
  name: 'Checkbox',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'flag',
    schemaProperties: { type: 'boolean' },
  },
}

enum-control

ts
{
  id: 'enum-control',
  name: 'Dropdown',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'choice',
    schemaProperties: { type: 'string', enum: ['Option A', 'Option B', 'Option C'] },
  },
}

date-control

ts
{
  id: 'date-control',
  name: 'Date Picker',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'date',
    schemaProperties: { type: 'string', format: 'date' },
  },
}

datetime-control

ts
{
  id: 'datetime-control',
  name: 'Date-Time Picker',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'dateTime',
    schemaProperties: { type: 'string', format: 'date-time' },
  },
}

multi-enum-control

ts
{
  id: 'multi-enum-control',
  name: 'Multi Select',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'selections',
    schemaProperties: { type: 'array', uniqueItems: true, items: { type: 'string', enum: [] } },
  },
}

array-control

ts
{
  id: 'array-control',
  name: 'Array',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'items',
    schemaProperties: { type: 'array' },
  },
}

object-control

ts
{
  id: 'object-control',
  name: 'Object',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'obj',
    schemaProperties: { type: 'object' },
  },
}

oneOf-control

ts
{
  id: 'oneOf-control',
  name: 'OneOf',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'variant',
    schemaProperties: { oneOf: [] },
  },
}

label

ts
{
  id: 'label',
  name: 'Label',
  category: 'display',
  nodeType: 'Label',
  defaults: { label: 'Label text' },
}

Generated UI Schema:

json
{ "type": "Label", "text": "Label text" }

Auto-Generated Property Names

When a Control manifest is used to create a new node, the builder automatically generates a unique property name based on the manifest's defaults.propertyName:

  • string-control generates text, text1, text2, etc.
  • number-control generates number, number1, number2, etc.
  • integer-control generates integer, integer1, integer2, etc.
  • boolean-control generates flag, flag1, flag2, etc.
  • enum-control generates choice, choice1, choice2, etc.
  • date-control generates date, date1, date2, etc.
  • datetime-control generates dateTime, dateTime1, dateTime2, etc.
  • multi-enum-control generates selections, selections1, selections2, etc.
  • array-control generates items, items1, items2, etc.
  • object-control generates obj, obj1, obj2, etc.
  • oneOf-control generates variant, variant1, variant2, etc.

The generatePropertyName() utility checks against all existing property names in the tree to avoid collisions.

Registering Manifests

Use the component registry to add custom manifests:

ts
import { useComponentRegistry } from '@banclo/jsonforms-core'

const registry = useComponentRegistry()

// Register a single manifest
registry.register({
  id: 'phone-input',
  name: 'Phone Number',
  category: 'control',
  nodeType: 'Control',
  defaults: {
    propertyName: 'phone',
    schemaProperties: {
      type: 'string',
      pattern: '^\\+?[1-9]\\d{1,14}$',
    },
    options: { format: 'tel' },
    label: 'Phone',
  },
})

// Retrieve all manifests
const all = registry.getAll()

// Retrieve by ID
const manifest = registry.getById('phone-input')

See Custom Components for complete examples.