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

Data Types

Overview

A data type is the single object that answers everything the CMS needs to know about one value: which input component edits it, which Zod schema validates it, which comparison modes its filter offers, and whether the field it sits on needs extra decorators. FormField.type and ColumnOptions.type both take one, which is why a form field and a table column of the same type behave identically.

import {
  CreateDataType,
  DataType,
  DefaultDataCompareTypes,
  DefaultDataTypes,
  RegisterDataType,
  serializeType,
} from "@antelopejs-private/cms/interfaces/cms-base/data-types";

The data-types subpath re-exports the built-in catalogues, so DefaultDataTypes, DefaultDataCompareTypes and StatusType are all reachable from it. They also have their own modules, data-types/default-types and data-types/status-type, which is how much of the CMS imports them.

Note that the data types are not re-exported from the cms-base package root — always import them from the data-types subpath.

Use a Data Type

Instantiate the type with its options and hand the instance to the field or the column.

import { Form } from "@antelopejs-private/cms/interfaces/cms-base";
import { DefaultDataTypes } from "@antelopejs-private/cms/interfaces/cms-base/data-types";

const form = Form({
  fields: [
    {
      id: "name",
      label: "Name",
      type: new DefaultDataTypes.StringType({ maxLength: 120 }),
      required: true,
    },
    {
      id: "status",
      label: "Status",
      type: new DefaultDataTypes.SelectType({
        items: [
          { label: "Draft", value: "draft" },
          { label: "Published", value: "published" },
        ],
      }),
    },
  ],
  submitUrl: "/api/articles/new",
});

An instance is reusable: the same object can back a form field and a table column, since it holds only its configuration.

The DataType Contract

abstract class DataType {
  constructor(
    readonly compareModes: Class<DataCompareMode>[] = [],
    readonly defaultCompareMode?: Class<DataCompareMode>,
    readonly options?: Record<string, unknown>,
  );

  protected abstract defaultInputComponent(): ComponentInfoSerialized;
  abstract getValidation(): z.ZodType;

  filter(context, proxy, key, value, mode, row): ValueProxyOrValue<boolean>;
  filterComponents(): Record<string, ComponentInfoSerialized> & { default: ComponentInfoSerialized };
  inputComponent(): ComponentInfoSerialized;
  decorateField?(target: unknown, key: PropertyKey, descriptor?: PropertyDescriptor): void;
}
MemberRole
compareModesCompare-mode classes this type offers in the filter dropdown.
defaultCompareModeThe mode selected when the viewer adds a filter on this field.
optionsThe type's own configuration, kept on the instance and readable by other code.
defaultInputComponent()Abstract. Returns the input component the type renders. Implemented by every concrete type.
getValidation()Abstract. Returns the Zod schema for one value of this type, before the field-level adaptation described in Forms.
inputComponent()The form input. Defaults to defaultInputComponent().
filterComponents()The filter inputs, keyed by compare-mode id with a mandatory default. Defaults to { default: defaultInputComponent() }.
filter()Applies a filter for one compare mode. The base implementation looks the mode up in the compare-mode registry and delegates to it; a type overrides it when a mode needs different semantics.
decorateField()Optional. Called by the @Column decorator so the type can add further decorators to the field — this is how the relation types install their join.

Write a Custom Type

StatusType is the smallest complete example: a boolean rendered as an online/offline indicator whose labels and colours are configurable.

import { z } from "zod";
import { FormComponents } from "@antelopejs-private/cms/interfaces/cms-base/form";
import {
  DataType,
  DefaultDataCompareTypes,
  RegisterDataType,
} from "@antelopejs-private/cms/interfaces/cms-base/data-types";

@RegisterDataType("status")
export class StatusType extends DataType {
  constructor(
    public readonly options: {
      onlineLabel?: string;
      offlineLabel?: string;
      onlineColor?: string;
      offlineColor?: string;
    } = {
      onlineLabel: "$common.status.online",
      offlineLabel: "$common.status.offline",
      onlineColor: "primary",
      offlineColor: "neutral",
    },
  ) {
    super(
      [DefaultDataCompareTypes.Is, DefaultDataCompareTypes.IsNot],
      DefaultDataCompareTypes.Is,
      options as Record<string, unknown>,
    );
  }

  protected defaultInputComponent() {
    const component = FormComponents.InputCheckbox();
    component.options = this.options;
    return component;
  }

  getValidation() {
    return z.boolean();
  }
}

Three pieces make it a type: the @RegisterDataType("status") decorator puts it in the registry under an id, the super() call declares the compare modes and stores the options, and the two abstract members supply the input and the schema. StatusType itself is exported by the data-types subpath and is one of the three column types a kanban board can group by — see Tables.

The Registry

RegisterDataType

const RegisterDataType: (id: string) => ClassDecorator<Class<DataType>>;

Registers the decorated class under id, in both directions: CreateDataType looks up the class by id, and getDataTypeId recovers the id from an instance. An id must be unique — registering twice under the same string replaces the earlier class.

RegisterDataCompareMode

const RegisterDataCompareMode: (
  id: string,
  noInput?: boolean,
) => ClassDecorator<Class<DataCompareMode>>;

Registers a compare mode. The decorator instantiates the class immediately and keeps that single instance, so a compare mode is stateless. noInput marks a mode that needs no value from the viewer — serializeType replaces its filter component with the literal "noInput".

DataCompareMode is the interface a mode implements:

interface DataCompareMode {
  filter(
    context: RequestContext,
    proxy: ValueProxy<unknown>,
    key: string,
    value: unknown,
    row: ValueProxy<Record<string, unknown>>,
  ): ValueProxyOrValue<boolean>;
}

proxy is the database value proxy for the field being filtered and row the proxy for the whole row, both from @antelopejs/interface-database, so a mode builds a database-side predicate rather than filtering in memory.

CreateDataType

function CreateDataType(id: string, options?: unknown): DataType;

Instantiates a registered type by id, forwarding options to its constructor. It asserts on an unknown id with the message Unknown type <id>. Use it when the type is chosen at runtime — from configuration, or from a description produced by another module — rather than written in the source.

getDataTypeId

function getDataTypeId(instance: DataType): string | undefined;

Returns the id an instance's class was registered under, or undefined when the class is not registered. It resolves the class from the instance's own prototype, so a subclass of a registered type returns its own id, not its parent's.

Serialization

function serializeType(instance?: DataType): DataTypeSerialized | undefined;

serializeType is what turns a type instance into the description shipped to the frontend. It returns undefined when there is no instance, and also when the instance's class is not registered.

DataTypeSerialized:

FieldTypeDescription
idstringThe registered id.
compareModesstring[]Ids of the type's compare modes; unregistered ones are dropped.
defaultCompareModestringId of the default mode.
filterComponentsRecord<string, FilterComponent>Filter inputs by mode id, from filterComponents(), with every noInput mode's entry replaced by the literal "noInput".
inputComponentComponentInfoSerializedThe form input, from inputComponent().

FilterComponent is ComponentInfoSerialized | "noInput". The value-level counterpart, ComponentOrNoInput, is described in Shared Types.

Compare Modes

DefaultDataCompareTypes holds the built-in modes. Each entry is a class; you reference the class in a type's compareModes, and the string id is what appears in a serialized type and in a filter query.

ClassIdInput
Isisvalue
IsNotis_notvalue
GreaterThangreater_thanvalue
GreaterThanOrEqualTogreater_than_or_equal_tovalue
LessThanless_thanvalue
LessThanOrEqualToless_than_or_equal_tovalue
IsBetweenis_betweentwo values
IsEmptyis_emptynone
IsNotEmptyis_not_emptynone
Includeincludevalue
Excludeexcludevalue
Containscontainsvalue
NotContainsnot_containsvalue
ArrayContainsStringarray_contains_stringvalue

IsEmpty and IsNotEmpty are the two registered with noInput. The namespace also exports escapeRegex(str: string): string, the helper the substring modes use to make a viewer-supplied value safe inside a pattern.

The Default Types

DefaultDataTypes is the built-in catalogue. Every class takes its options object as its only constructor argument, and most of them accept a fallback string used in place of an empty value.

ClassIdCompare modes (default first)Input
StringTypestringcontains, is, is_not, not_contains, is_empty, is_not_emptytext or textarea
NumberTypenumberis, is_not, greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to, is_empty, is_not_emptynumber
PriceTypepriceas NumberTypenumber
PercentageTypepercentageas NumberTypepercentage
DateTypedateis, less_than, greater_than, is_between, is_empty, is_not_emptydate picker
BooleanTypebooleanis, is_not, is_empty, is_not_emptyswitch
SelectTypeselectinclude/exclude when multiple, otherwise is/is_not; plus is_empty, is_not_emptyselect
EmailTypeemailcontains, is, is_not, not_contains, is_empty, is_not_emptyemail
UrlTypeurlis, is_not, is_empty, is_not_emptytext
PhoneTypephonecontains, is, is_not, not_contains, is_empty, is_not_emptyphone
ColorTypecoloris, is_not, is_empty, is_not_emptycolour
PasswordTypepasswordnonepassword
RichTextTyperich_textnonerich text
StringTimeTypestring_timeas NumberTypetime
TreeTypetreeinclude, excludetree
PermissionsTypepermissionsinclude, excludepermissions tree
RelationTyperelationis, is_not, is_empty, is_not_emptyrelation picker
CascaderRelationTypecascader_relationis, is_not, is_empty, is_not_emptycascader
AddressTypeaddressis_empty, is_not_emptyaddress
FileTypefilenonefile upload
ImageTypeimagenoneimage upload

Scalar Types

Options interfaceFields
StringTypeOptionsplaceholder?, maxLength?, minLength?, textarea?, rows?, fallback?
NumberTypeOptionsmin?, max?, step?, placeholder?, fallback?
DateTypeOptionsrange?, multiple?, minDate?, maxDate?, fallback?
BooleanTypeOptionsfallback?
SelectTypeOptionsitems: FormComponents.SelectOption[], placeholder?, multiple?, deselectable?, fallback?
EmailTypeOptions, ColorTypeOptions, UrlTypeOptions, RichTextTypeOptionsplaceholder?, fallback?
PasswordTypeOptionsplaceholder?, minLength?, confirmPassword?, confirmPlaceholder?, fallback?
PhoneTypeOptionsplaceholder?, requiredPrefix?, fallback?
StringTimeTypeOptionsplaceholder?, min?, max?, fallback?
TreeTypeOptionsitems?: TreeNode[], fetchUrl?, placeholder?, multiple?, fallback?
PermissionsTypeOptionsfetchUrl?, fallback?

Notes on the ones whose behaviour is not obvious from the options:

  • StringType renders a textarea instead of a single-line input when textarea is set, and passes rows to it.
  • DateType overrides filter so is, is_not and is_between compare whole UTC days rather than instants, and adds a range picker as the filter component for is_between. Its schema accepts a Date or an ISO-shaped string and transforms it to a Date; with range or multiple the value becomes an array.
  • BooleanType uses a two-option select as its filter component, so a viewer picks "checked"/"unchecked" rather than typing a value.
  • SelectType validates against the values of its items, as an enum or an array of that enum when multiple is set.
  • PriceType is a NumberType whose options default to min: 0 and step: 0.01.
  • PercentageType is a NumberType whose options default to min: 0, max: 1 and step: 0.01; the stored value is a fraction while the input works in whole percent.
  • PhoneType only validates the E.164 pattern when requiredPrefix is set; otherwise any string passes.
  • ColorType validates a six-digit #RRGGBB string.
  • StringTimeType validates a number — a time of day expressed as a number, bounded by min and max.

Relations

RelationType<T> points a field at another data controller. Its input is a searchable picker whose searchUrl is the target controller's select endpoint, and — unless filterOnly is set — its decorateField installs a data-api Foreign join so list rows carry the resolved related row instead of the raw foreign key.

import { DefaultDataTypes } from "@antelopejs-private/cms/interfaces/cms-base/data-types";
import { Column } from "@antelopejs-private/cms/interfaces/cms-base/table-view";

@Column({
  name: "Customer",
  type: new DefaultDataTypes.RelationType({
    dataApiController: customerDataAPI,
    keyMapping: { label: "name", value: "_id" },
  }),
  filterable: true,
})
declare customerId: string;
OptionTypeDescription
dataApiControllerT extends ControllerClassThe target data controller. Required.
keyMapping{ label?, value?, avatar?, disabled? }Which of the target's fields fill each role in the picker. Keys are checked against the target's own field names.
indexstringIndex the join reads, when it is not the primary key.
placeholderstringPicker placeholder.
multiplebooleanAllow more than one related row.
deselectablebooleanAllow clearing the selection.
filterOnlybooleanSkip the join entirely: the raw scalar id stays in the list output and the funnel filter runs equality on it.
fallbackstringText used in place of an empty value.

When the target controller exposes a new endpoint and has form fields, the picker also carries an inline "add" form built from the target's new fields, plus the permission id of the target's add action.

CascaderRelationType<T> is the same idea for a self-referencing table: the target's select endpoint returns a flat list, and the widget rebuilds the tree client-side. Its keyMapping is required and adds parent — the field pointing at the parent row, which must itself carry @Select() — alongside the required label and value. It adds maxDepth and leafOnly to the relation options, and supports the same filterOnly behaviour.

Address

AddressType<T> stores a structured postal address. Its Address shape follows ISO 19160-1 and maps directly onto the UBL cac:PostalAddress used by EN 16931 / Peppol BIS Billing 3.0.

FieldRequiredDescription
streetNameyesStreet or thoroughfare name.
postalCodeyesPostal or ZIP code.
cityyesCity or locality.
countryCodeyesISO 3166-1 alpha-2 country code.
houseNumbernoHouse or building number.
boxNumbernoBox, unit or apartment number.
addressLine2noSupplementary delivery information.
countrySubdivisionnoRegion, state or province.

The type's options are placeholder (one string per address component), autocomplete, an optional dataApiController with a keyMapping of label / value for picking an existing address, and fallback. AddressAutocomplete configures the geocoder:

OptionTypeDescription
enabledbooleanEnable suggestions on the street field. Defaults to false.
urlstringGeocoder endpoint. Defaults to "https://photon.komoot.io/api".
langstringPreferred result language, ISO 639-1. Falls back to the UI locale.
limitnumberMaximum number of suggestions. Defaults to 5.
placeholderstringSearch field placeholder.

Files and Images

FileTypeOptions has multiple?, constraints?: UploadConstraints, path?, storage? and fallback?. ImageTypeOptions has multiple?, max?, constraints?, path?, storage? and resize?, the FormComponents.ImageResizeOptions the input applies client-side before uploading (Forms). constraints is the UploadConstraints type from @antelopejs/interface-file-storage; storage names the storage backend and path the prefix uploads land under.

A file value is the resource key as a string; the schema validates it against the constraints by reading the uploaded object's metadata, matching allowedMimetypes patterns including the type/* form. A key pointing at no real upload fails validation like any other invalid input, while a metadata read that fails for another reason — a storage outage, an auth error — propagates as a server error rather than being reported as a bad field. An image value is an ImageValue{ key: string; alt?: string; principal?: boolean } — and ImageType defaults allowedMimetypes to ["image/*"], caps the array at max when multiple is set, and rejects a list with more than one principal image.

Both types are what makes the table view's file handling apply to a column: staged uploads are promoted on write and orphaned objects deleted after an edit or a delete. The helpers behind that are described in Helpers.

Next Steps

  • Helpers - Search, export jobs, file references and the archive-mode defaults.