src/custom-fields/ and default-export a
defineCustomFieldsConfig. The config targets a model, optionally fetches linked
module data alongside it, and describes what to add to each surface.
Add a custom field
1
Create a custom-fields file
Create a file under
src/custom-fields/ and default-export
defineCustomFieldsConfig with the model you want to extend. Start with
product.apps/vendor/src/custom-fields/product.tsx
2
Describe the field with createFormHelper
Import
createFormHelper from @mercurjs/dashboard-shared and turn a Zod
schema into an input type and validation. The Zod schema drives both the
default input and the validation.apps/vendor/src/custom-fields/product.tsx
3
Add the field to a form
Add the field to a built-in form zone and tab. This injects an
ERP ID field
into the product edit form.apps/vendor/src/custom-fields/product.tsx
4
Run the panel
Start the panel and open the product edit form. The field renders in its tab.For
Terminal
product, values submit under additional_data and persist onto the
product’s metadata. See Persistence for other models.Configuration
defineCustomFieldsConfig takes one object.
Add fields to a form
Inject fields into a built-in create, edit, or onboarding form.CustomFormField.
Replace or hide a built-in field
Afields key that matches one of the form’s own fields overrides it instead of
adding a new one. null hides the field. A field definition replaces how it
renders, and the replacement still writes to the built-in form key.
Fields in tabbed create forms
In a tabbed create form, settab to place a field on a tab. A field without a
tab renders on the first tab. Continue validates the custom fields of the tab
you leave, and Publish validates them all.
details, organize, attributes, and
variants. Product-level values reach the productsCreated hook of
createProductsWorkflow as additional_data.
Add columns to a spreadsheet tab
Some forms are spreadsheets, such as the product create form’svariants tab or
the offer create form’s stockLevelsAndPrices tab. Target the tab and the same
rules apply to its columns: a built-in column id with null hides it, a
definition replaces it, and any other key adds an editable column. Added values
are stored per row under additional_data. A spreadsheet tab only takes fields
that name it in tab; untabbed fields never become columns.
A replaced spreadsheet column with a
component is read-only and receives the
row as data. Without a component, it keeps editing the built-in value with the
cell for its type.
Customize the onboarding wizard
The vendor onboarding wizard is theonboarding form zone of the seller model. Each step is a tab. Use the same forms entries to add fields to a step, add a step, remove a step, or reorder the steps.
apps/vendor/src/custom-fields/seller.tsx
The built-in steps and their defaults:
The last step’s button reads Finish and creates the seller. Values from every step’s custom fields are merged into one
additional_data object on POST /vendor/sellers, which reaches the sellerAccountCreated hook of createSellerAccountWorkflow. A skipped step contributes nothing, even if its fields are required.
Always set
tab on an onboarding entry. An entry without a tab renders its fields on every step.Extend detail sections
Add, replace, or remove fields on a detail section, and add actions to itsActionMenu.
fields[] is CustomDisplayField.
A display
component receives the loaded detail entity as data, including any
linked module data. If the config declares link: "brand", read it off
data.brand.
actions[] is SectionAction, the same shape as list bulkActions.
Extend the list table
Override or add columns, register bulk actions and filters, and set view defaults on the model’s list table.columns[] is CustomColumn.
filters[] is CustomListFilter. The key is the query param the filter
writes, so it matches a built-in filter by its param name (customer_id,
status, created_at, …).
A replacement keeps the built-in filter’s position; added filters go after the
built-in ones.
Extend other tables of the model
Alist block without a zone targets the model’s list page. Other tables of
the same model, such as a detail-page section or a picker inside a create form,
have their own zone. Pass an array to extend several tables. The zones and
column ids are typed per model.
Every table keeps its row selection checkbox as the first column and its row
actions menu as the last; extensions add and reorder columns between them.
Bulk-action rendering is deferred in the MVP.
bulkActions are accepted and
surfaced by the config, but not yet mounted into the list toolbar.Combine configs from plugins and your app
Every config for a model applies in order: installed plugins first, then your app’ssrc/custom-fields/ files. A later config sees everything earlier configs
added, not only the built-ins, so your app can change what a plugin contributed.
link, bulkActions, display actions, activity, and actionLocks add up
across configs.
Fetch linked module data
Declarelink to fetch a module link alongside the entity. Its data rides on the
data passed to displays and on the row passed to columns.
Persistence
Next steps
Widgets
Render a component in a slot on an existing page.
Create a new page
Add a route with file-based routing and register it in the sidebar.