[data-reveal]{opacity:1!important;transform:none!important}
Components

Forms

Declare fields, validation, and fetch/submit URLs; the frontend renders the inputs and the submit flow.

Form is the component builder for data-entry screens. You declare a list of fields — each labelled and typed by a DataType (Data types) — plus where the form loads its values from and submits them to. The shared Nuxt frontend turns that into rendered inputs, client-side validation, and the submit flow. You write no Vue.

Use a Form for create/edit screens, settings panels, and standalone data entry. For paginated, sortable, CRUD-backed lists whose add/edit forms are generated from a Data controller, use a TableView (Tables) instead.

Form is a page component: assign the builder to a static field on a PageController (see Pages & components). The field key becomes the component key and part of its permission id.

What it renders

The frontend renders a titled card. From the top:

  • An optional title and description header.
  • The fields, each a labelled input chosen from its DataType (text box, select, date picker, file upload, rich text, …), with the field's description as helper text. Required fields are marked; invalid fields show an inline message and an error state.
  • Field groups, whose members render together under a shared label, laid out horizontally or vertically (orientation).
  • A submit button at the bottom. On submit the form validates against its schema; on success it shows successMessage (and optionally navigates via redirectOnSuccess), on failure errorMessage.

When fetchUrl is set the form first loads existing values and pre-fills the inputs (an edit form); without it the form starts empty (a create form).

Usage

Assign the builder to a static field on the page.

src/pages/contact.ts
import {
  PageController,
  RegisterPage,
  pagesCategory,
} from "@antelopejs-private/cms/interfaces/cms/page";
import { Form } from "@antelopejs-private/cms/interfaces/cms-base/form";
import { DefaultDataTypes } from "@antelopejs-private/cms/interfaces/cms-base/data-types";
import { HttpMethod } from "@antelopejs-private/cms/interfaces/cms-base/types";

@RegisterPage()
export class ContactPage extends PageController("contact", {
  displayName: "Contact",
  icon: "i-ph-note-pencil",
  category: pagesCategory,
}) {
  static form = Form({
    title: "Contact Us",
    description: "Send us a message and we'll get back to you",
    fields: [
      {
        id: "name",
        label: "Full Name",
        type: new DefaultDataTypes.StringType({ placeholder: "Jane Doe", maxLength: 100 }),
        required: true,
      },
      {
        id: "email",
        label: "Email",
        type: new DefaultDataTypes.EmailType({ placeholder: "[email protected]" }),
        required: true,
      },
      {
        id: "message",
        label: "Message",
        type: new DefaultDataTypes.StringType({ textarea: true, rows: 6, maxLength: 1000 }),
      },
    ],
    submitUrl: "/api/contact",
    submitUrlMethod: HttpMethod.post,
    successMessage: "Thanks — we'll be in touch.",
  });
}

The routes behind submitUrl (and fetchUrl) are normal API controllers — see Pages & components for wiring. Return _id, never id, for created rows.

Options

Form(options) takes a single options object (FormProps):

OptionTypeDefaultPurpose
fields(FormField | FieldGroup)[](required)Fields and field groups to render. See Fields.
titlestringHeading above the fields (no heading is rendered when unset); the component's display name falls back to "Form".
descriptionstringSub-heading text under the title.
fetchUrlstringURL to load existing values from (pre-fills the form). Omit for a create form.
fetchUrlMethodHttpMethodGETHTTP method for the fetch request.
submitUrlstringURL the form submits to.
submitUrlMethodHttpMethodPUTHTTP method for the submit request.
submitLabelstringLabel of the submit button (i18n key or literal). Defaults to the generic "Save changes".
successMessagestringToast shown after a successful submit.
errorMessagestringToast shown when a submit fails.
fieldsOrientation"horizontal" | "vertical"Overall layout direction of the field list.
redirectOnSuccessstringPath to navigate to after a successful submit. Supports token substitution.
submitDefaultsRecord<string, unknown>Extra values merged into the submit payload. String values support token substitution.

submitUrl, redirectOnSuccess, and submitDefaults string values support token substitution, resolved against the form's route at submit time:

  • {{params.X}} — a route path parameter.
  • {{query.X}} — a query-string parameter.
  • {{response.X}} — (redirectOnSuccess only) a field of the submit's JSON response body, e.g. /items/{{response._id}}/edit to redirect to a freshly created entity.

submitDefaults entries whose tokens can't be resolved are dropped — this is how a page-rendered "new" form inherits context such as a parent id.

HttpMethod is a string enum (HttpMethod.get, HttpMethod.post, HttpMethod.put, HttpMethod.delete, HttpMethod.patch, …). The options require the enum member — a bare string literal such as "POST" does not typecheck.
When submitUrlMethod is omitted, the frontend submits with PUT — a creation form must set submitUrlMethod: HttpMethod.post explicitly.

Fields

Each entry in fields is either a single field (FormField) or a field group (FieldGroup).

Field

{
  id: "email",                                   // payload key — required
  label: "Email Address",                        // visible label
  description: "We never share it",              // helper text under the label
  type: new DefaultDataTypes.EmailType({ ... }), // a DataType instance — required
  required: true,                                // marks the field and tightens validation
  defaultValue: "[email protected]",              // initial value
  localized: true,                               // store one value per locale
  disabled: true,                                // render read-only
  inputComponent: FormComponents.InputText(),    // override the DataType's default input
}
PropertyTypePurpose
idstringThe field's key in the payload. Required.
typeDataTypeThe DataType instance driving the input, parsing, and validation. Required.
labelstringField label. Use a $-prefixed i18n key in real modules.
descriptionstringHelper text under the label.
requiredbooleanMarks the field required and tightens validation (see Validation).
defaultValueDefaultValueInitial value (string | number | boolean | Date | null | array).
localizedbooleanStore the value as a per-locale record instead of a single scalar.
disabledbooleanRender the input non-editable.
inputComponentComponentInfoSerializedOverride the input the DataType would otherwise pick (see below).

The type is always a DataType instanceStringType, EmailType, SelectType, DateType, FileType, RichTextType, AddressType, and so on. Each carries its own options (placeholders, min/max, items, constraints) and supplies both the validation rule and the default input. See Data types.

Field group

A group renders related fields together under a shared label:

{
  id: "contactGroup",
  label: "Contact Information",
  description: "Your email and phone number",
  orientation: "vertical",   // or "horizontal"
  order: 1,                   // sort order among items
  fields: [
    { id: "email", type: new DefaultDataTypes.EmailType(), required: true },
    { id: "phone", type: new DefaultDataTypes.PhoneType() },
  ],
}

Grouped fields still submit as flat top-level keys (email, phone) — the group only affects layout. Members may omit label; the group's own label heads the cluster.

Overriding the input component

Each field renders the input its DataType chooses. Set inputComponent to override it with a factory from the FormComponents namespace (same import as Form), each taking its own options object:

  • Text & numbersInputText, InputTextarea, InputEmail, InputPassword, InputPhone, InputNumber, InputPercentage, InputSlider, RichText.
  • ChoicesInputSelect, InputRadioGroup, InputSwitch, InputCheckbox, InputCascader, InputTree, InputRelation.
  • Dates & timesDatePicker, DatePickerRange, Calendar, InputTime.
  • Media & specializedFile, Image, InputColor, InputAddress, PermissionsTree.

Prefer configuring the DataType; reach for inputComponent only when you need a different control than the type's default.

Features

Validation

The form validates on the client before submit, against rules derived from the fields: each field's DataType supplies its rule, then:

  • Required string and array fields must be non-empty (min length 1); other required fields must be non-null. Non-required fields are nullable and optional.
  • Localized fields validate as a record of locale → value, so each provided translation is checked against the field's rule.
The schema derives purely from the fields and their DataTypes — there is no separate validation option. To change a rule, configure the DataType or the field's required flag.

Localized fields

Set localized: true to store one value per locale: the value becomes a record keyed by locale code (e.g. { en: "Title", fr: "Titre" }) and the input renders a per-language editor. This is for content translated per locale — distinct from $-prefixed i18n message keys used for static labels (Localization).

Watches and reactivity

Watch-actions let one component react to another's events (e.g. conditional fields). .watch() below is the form-flavored use of a generic builder API — the full reference (including .watchOn(), watchFilter, and the events every component emits) is Actions & reactivity; the frontend engine and custom function handlers are in Component events.

A form reacts to its own events through .watch() on the builder. A watch listens for a form event and runs a form function, optionally gated by a condition or a permission:

src/pages/demo.ts
import {
  Form,
  FormEvents,
  FormFunctions,
} from "@antelopejs-private/cms/interfaces/cms-base/form";

static form = Form({
  fields: [
    { id: "isOwner", label: "Is Owner", type: new DefaultDataTypes.BooleanType(), defaultValue: false },
    { id: "displayName", label: "Display Name", type: new DefaultDataTypes.StringType() },
  ],
})
  .watch(FormEvents.FIELD_CHANGE, FormFunctions.SET_FIELD_DISABLED, {
    params: { targetField: "displayName", setDisabled: true },
    onParam: [
      { key: "fieldId", value: "isOwner" },
      { key: "value", value: true },
    ],
  });
  • Events (FormEvents): SUBMIT, SUBMIT_SUCCESS, SUBMIT_ERROR, FIELD_CHANGE, RESET.
  • Functions (FormFunctions): SET_FIELD_DISABLED ({ targetField, setDisabled }), SET_FIELD_HIDDEN ({ targetField, setHidden }), and SET_FIELD_REQUIRED ({ targetField, setRequired }) — the last one toggles a field's required state in the client-side validation (a hidden or disabled field is never required).
  • onParam — one condition or an array of conditions ({ key, value }) that must all match the event payload for the function to run. For FIELD_CHANGE the payload carries fieldId and value, so the example above disables displayName only when isOwner changes to true.
  • requirePermission — only apply this watch if the calling user holds the given permission id (see Permissions).
  • .watchFilter((permissions, watches, permissionId) => watches) — a final hook to filter the watch list per user at serialize time.
Declaring watchActions inside the options object is silently stripped by the builder — those watches never reach the frontend. Use .watch() on the builder.

Form modes and readonly behavior

A standalone Form has exactly two modes, decided by fetchUrl: present means edit, absent means create. The new / edit / view form modes and the per-mode readonly behavior (disabled / hidden / default) belong to the data-controller-generated add/edit forms behind a TableView (Tables) — they are not props on the standalone Form builder. For per-field create/edit/view behavior, model the data with a DataController (Data controller).

Permissions

A Form is a page component: its permission id is the page's fullId (category chain + page id) joined with every field/child id in its position — e.g. a form field on the page whose fullId is pages.form.form-grouped resolves to pages.form.form-grouped.form, while a form nested as details under panel resolves to pages.form.form-grouped.panel.details. That id is also what a watch's requirePermission references. Derivation rules, gating the backing routes, and registering standalone permissions are covered in Auth & permissions. A Form gates at the component level — it does not assign a permission per field.