The schema shape
FormBuilderSchema is the JSON contract between the visual builder, your backend, and ngs-form-renderer. The required property is sections; top-level fields and layout are optional, but useful when a form mixes standalone fields and sections.
const schema: FormBuilderSchema = {
title: 'Customer intake',
fields: [
{
id: 'source',
name: 'source',
type: 'select',
label: 'Source',
width: 6,
options: [
{ label: 'Website', value: 'website' },
{ label: 'Referral', value: 'referral' },
],
},
],
sections: [
{
id: 'contact',
title: 'Contact',
description: 'Basic customer contact details.',
fields: [
{
id: 'email',
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'name@example.com',
required: true,
width: 6,
},
],
},
],
layout: [
{ kind: 'field', id: 'source' },
{ kind: 'section', id: 'contact' },
],
};Top-level properties
titleis optional metadata for your app, storage, or surrounding page UI.fieldsstores top-level fields that are not inside a section.sectionsstores named groups of fields and is always present.layoutstores the visual order of top-level fields and sections.
If layout is missing, the renderer appends top-level fields first and then sections. If layout contains stale ids, they are ignored.
Sections
A section has an id, title, optional description, optional collapsed state, and a fields array. Use sections for visible form groups such as Contact, Billing, Security, or Approval details.
Fields
A field's id is for builder layout and selection. Its name is the Angular form control key and the value key emitted by the renderer. The type must match a registered field definition, either one of the built-in fields or a custom field registered with provideFormBuilderField.
{
id: 'email',
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'name@example.com',
hint: 'Used for account notifications.',
defaultValue: '',
required: true,
width: 6,
validation: [
{ type: 'email', message: 'Use a valid email address.' },
],
}Layout ordering
The layout array controls only top-level ordering. Section field order is stored inside each section's fields array, and nested container order is stored inside children.
layout: [
{ kind: 'field', id: 'source' },
{ kind: 'section', id: 'contact' },
];Options and defaults
Choice fields use options. The renderer uses defaultValue when it exists; otherwise selected options become the initial value for select, radio, and multiple-choice fields.
options: [
{ label: 'Starter', value: 'starter' },
{ label: 'Growth', value: 'growth', selected: true },
{ label: 'Enterprise', value: 'enterprise' },
]Validation
required adds required validation. The validation array adds declarative rules such as email, minLength, maxLength, min, and max. A custom field definition can override this with its own validators factory.
Settings
settings is the extensibility bag. Built-in fields use it for values such as upload accept types, repeater empty text, radio orientation, and static spacer height. Custom fields should store renderer-specific configuration there too.
Nested fields
Layout fields such as group and repeater use children. Groups flatten child controls into the current form group. Repeaters create a FormArray of row groups from their child fields.
{
id: 'team-members',
name: 'teamMembers',
type: 'repeater',
kind: 'layout',
label: 'Team members',
width: 12,
settings: {
allowNullValue: false,
emptyText: 'No team members yet.',
},
children: [
{ id: 'member-name', name: 'name', type: 'text', label: 'Name', width: 6 },
{ id: 'member-email', name: 'email', type: 'email', label: 'Email', width: 6 },
],
}Storage checklist
- Persist the whole
FormBuilderSchemaas JSON. - Keep field
idvalues stable so layout references remain valid. - Keep field
namevalues stable when submitted data already exists. - Store custom renderer options under
settings. - Version schemas in your backend when production forms change over time.