What you build
A Form Builder field has two parts: a field definition that appears in the palette and a renderer component that receives a FormBuilderField and an Angular FormControl. The definition can also include a settings schema, so admin users can configure labels, defaults, and renderer-specific options from the inspector.
Import the public API
Consumer code should import from the secondary entry point. The builder, schema types, field types, and provider helpers are exported from @ngstarter-ui/components/form-builder.
import {
FormBuilder,
FormBuilderField,
FormBuilderSchema,
provideFormBuilderField,
} from '@ngstarter-ui/components/form-builder';Create the field renderer
A renderer is a standalone Angular component. It reads display data from field() and binds the provided control() to NgStarter form components. In this example the custom priority field uses ButtonToggle.
import { Component, input } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ButtonToggle, ButtonToggleGroup } from '@ngstarter-ui/components/button-toggle';
import { FormBuilderField } from '@ngstarter-ui/components/form-builder';
@Component({
selector: 'app-priority-field',
imports: [ReactiveFormsModule, ButtonToggle, ButtonToggleGroup],
template: `
<label>{{ field().label }}</label>
<ngs-button-toggle-group [formControl]="control()">
<ngs-button-toggle value="low">{{ field().settings?.['lowLabel'] || 'Low' }}</ngs-button-toggle>
<ngs-button-toggle value="medium">{{ field().settings?.['mediumLabel'] || 'Medium' }}</ngs-button-toggle>
<ngs-button-toggle value="high">{{ field().settings?.['highLabel'] || 'High' }}</ngs-button-toggle>
</ngs-button-toggle-group>
`,
})
export class PriorityField {
readonly field = input.required<FormBuilderField>();
readonly control = input.required<FormControl>();
}Register the field
Register one field with provideFormBuilderField, or use provideFormBuilder when you want to register multiple fields, layout items, settings components, or a global upload callback in one place.
providers: [
provideFormBuilderField({
type: 'priority',
label: 'Priority',
group: 'Workflow',
icon: 'fluent:flag-24-regular',
defaults: {
label: 'Priority',
width: 6,
defaultValue: 'medium',
settings: {
lowLabel: 'Low',
mediumLabel: 'Medium',
highLabel: 'High',
},
},
renderer: () =>
import('./priority-field/priority-field').then(c => c.PriorityField),
}),
]Add settings for the inspector
The settings config can inherit a built-in settings group and append a schema for custom options. Use dotted names such as settings.lowLabel when the value belongs in the field's custom settings bag.
provideFormBuilderField({
type: 'priority',
label: 'Priority',
group: 'Workflow',
icon: 'fluent:flag-24-regular',
defaults: {
label: 'Priority',
width: 6,
settings: {
lowLabel: 'Low',
mediumLabel: 'Medium',
highLabel: 'High',
},
},
renderer: () =>
import('./priority-field/priority-field').then(c => c.PriorityField),
settings: {
extends: 'field',
schema: {
sections: [
{
id: 'priority-settings',
title: 'Priority labels',
fields: [
{ id: 'priority-low-label', name: 'settings.lowLabel', type: 'text', label: 'Low label' },
{ id: 'priority-medium-label', name: 'settings.mediumLabel', type: 'text', label: 'Medium label' },
{ id: 'priority-high-label', name: 'settings.highLabel', type: 'text', label: 'High label' },
],
},
],
},
},
})Use the custom field in a schema
Saved schemas only need a field whose type matches the registered definition. The renderer and builder both resolve the definition at runtime, so the JSON remains compact and backend-friendly.
readonly schema = signal<FormBuilderSchema>({
title: 'Support request',
sections: [
{
id: 'request',
title: 'Request',
fields: [
{
id: 'priority',
name: 'priority',
type: 'priority',
label: 'Priority',
defaultValue: 'medium',
width: 6,
settings: {
lowLabel: 'Low',
mediumLabel: 'Medium',
highLabel: 'High',
},
},
],
},
],
});Render the builder
Bind the schema with two-way model syntax. When users drag the custom field or edit its settings, the schema signal receives the updated JSON.
<ngs-form-builder [(schema)]="schema" />Custom field checklist
- Give every field type a unique
type. - Set
defaultsfor label, width, default value, and custom settings. - Use NgStarter controls inside renderer components where a matching component exists.
- Use
settings.schemafor inspector controls instead of custom settings UI. - Keep saved form data in
FormBuilderSchema, not in component-only state.