Skip to main content
defineCustomFieldsConfig is Mercur’s model-scoped extension surface. From one file per model you add validated fields to built-in create and edit forms, replace, remove, or add fields in detail sections, and add columns to the list table. Everything wires into the built-in page and stays typed against the model’s generated registry.
UI, not schema. This helper is a panel surface. It renders, validates, and displays fields. It does not create database columns. To store extra data, use the backend Custom Fields module or your own API route or workflow. In the MVP, panel custom fields for product are submitted under additional_data and persisted onto the product’s metadata.

What you’ll build

An ERP ID field on the vendor product edit form, shown in the product’s detail section and as a list-table column, all from a single src/custom-fields/product.tsx.

Register the typed targets

Models, form zones, display zones, and built-in field ids are typed per panel. You register them once, and create-mercur-app ships this reference for you.
apps/vendor/src/extension-targets.d.ts

Build the config

1

Create the model file

Drop src/custom-fields/<model>.tsx and default-export a defineCustomFieldsConfig. createFormHelper from @mercurjs/dashboard-shared turns a Zod schema into an input type plus validation.
apps/vendor/src/custom-fields/product.tsx
2

Add a detail-section display

displays[] targets a detail-page section by its zone id. Keyed by field id, an entry adds, replaces, or removes a field.
apps/vendor/src/custom-fields/product.tsx
Built-in field ids such as subtitle, handle, status, and title autocomplete from the panel’s generated CustomFieldsRegistry. An unknown id is treated as an added row.
3

Add a list column

The list block extends the model’s list table. Add or override columns by id, hide built-in columns, and reorder.
apps/vendor/src/custom-fields/product.tsx
4

Reload the panel

Open the vendor portal. The product edit drawer shows the ERP ID field, validated on submit and persisted via additional_data. The detail general section shows the ERP ID row, with subtitle removed and handle re-rendered. The product list shows the ERP column.

The createFormHelper surface

createFormHelper<T>() exposes a Zod-based surface that drives both the input type and its validation.
Fields render through the standard Form.Field → Form.Item chain, never a raw Controller. They participate in the existing TabbedForm and RouteDrawer submit and validation flow.

Linked-module data

To read data from a linked module alongside the entity, declare it with link. Those relations are fetched with the entity and become available to columns and displays.
The SDK derives the fetch query from link and merges it into the built-in query with the + and - convention. You never hand-write the field list.

Verify

  1. The ERP ID field renders in the product edit drawer and validates on submit.
  2. Saving persists the value, visible on reload, via additional_data to metadata.
  3. The detail general section shows the ERP row, hides subtitle, and re-renders handle.
  4. The product list shows the ERP column, hides collection, and reorders columns.
  5. Set zone: "nope" in forms. bun run lint (tsc) fails against the model’s registry.

FAQ

Today: the product model in the vendor portal, with form zone edit and display zone general. The valid set per panel is generated into CustomFieldsRegistry in extension-targets.d.ts. Autocomplete model and zone to see what’s mounted.
In the MVP, product custom fields are submitted under additional_data and persisted onto product.metadata. defineCustomFieldsConfig itself doesn’t create a column. For durable, queryable storage, model it with the backend Custom Fields module or a custom route or workflow.
That’s the same helper with zone: "onboarding" and tab set to a wizard step id (vendor only). It’s designed but not mounted in the current MVP. The runtime host exists. The wizard mount is a follow-up.
Yes. A block can include src/custom-fields/ files in its vendor_ui or admin_ui entry, aggregated like the host app’s.

Next steps

Custom Fields module

Persist extra data on an entity with a generated side table.

Add a widget

Inject a component at a built-in zone.