All articles
Form Builder
Custom fields
Angular forms

How to add and configure a custom field in Form Builder

Learn how to add a custom field to NgStarter Form Builder, render it inside saved forms, and expose configuration controls in the builder inspector with a schema-driven settings panel.

June 18, 202610 min read

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.

imports.ts
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.

priority-field.ts
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.

field-provider.ts
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.

field-settings.ts
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.

schema.ts
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.

builder.html
<ngs-form-builder [(schema)]="schema" />

Custom field checklist

  • Give every field type a unique type.
  • Set defaults for label, width, default value, and custom settings.
  • Use NgStarter controls inside renderer components where a matching component exists.
  • Use settings.schema for inspector controls instead of custom settings UI.
  • Keep saved form data in FormBuilderSchema, not in component-only state.