component you pass in
receives at render time.
Separate apps, no
surface field. Admin (@mercurjs/admin, port 7000) and
vendor (@mercurjs/vendor, port 7001) are separate Vite apps. A file dropped
under a panel’s src/ targets that panel — the folder you author in is the
surface. The helpers are the same in both; import them from
@mercurjs/dashboard-sdk (config helpers) and @mercurjs/dashboard-shared
(createFormHelper).Typed targets (register once)
Zone ids, nav item ids, models, and built-in field ids are typed per panel from a generatedextension-targets.d.ts. Reference it once per host app so every
extension file type-checks with no per-file import:
apps/vendor/src/extension-targets.d.ts
apps/admin-test/src/extension-targets.d.ts
zone, model, or nav id fails tsc (bun run lint) rather than
silently no-op’ing at runtime.
defineWidgetConfig
A widget is a React component attached to a named zone on a built-in page. The
placement (before | after | replace) is the last segment of the zone id.
apps/vendor/src/widgets/product-list-banner.tsx
Config
| Field | Type | Description |
|---|---|---|
zone | WidgetZoneId | WidgetZoneId[] | Target zone(s). Placement is the suffix. Multiple widgets stack in order. |
id | string (optional) | Stable id; derived from the file path at build time when omitted. |
Component props
The widget component receives a single prop:| Prop | Type | Description |
|---|---|---|
data | unknown | The zone’s contextual entity — e.g. the loaded product on a product.detail.* zone. Undefined on list/public zones that have no single entity. |
The public
login.logo / login.before / login.after zones render before
authentication and receive no data.defineNavigationConfig
Reorder, hide, relabel, or re-parent built-in sidebar items. Single
host-owned file — blocks cannot contribute nav overrides.
apps/vendor/src/_navigation.ts
NavItemOverride
| Field | Type | Description |
|---|---|---|
id | NavItemId | Built-in item to override (top-level or nested). Typed against the panel registry. |
rank | number (optional) | Order within the item’s parent (or among top-level items). |
hidden | boolean (optional) | Remove from the sidebar (route may still be directly reachable). |
label | string (optional) | i18n key or literal replacing the item’s label. |
icon | ComponentType (optional) | Icon component replacing the item’s icon (from @medusajs/icons). |
nested | NavParentId | null (optional) | Re-parent under a built-in parent id; null promotes a nested item to top level. |
component prop here — navigation overrides reshape existing items
only.
defineCustomFieldsConfig
The model-scoped surface. One file per model adds form fields, section displays,
section actions, and list-table columns. Import
createFormHelper from @mercurjs/dashboard-shared to turn a Zod schema into an
input type + validation.
apps/vendor/src/custom-fields/product.tsx
Top-level config
| Field | Type | Description |
|---|---|---|
model | CustomFieldModel | Target model (start with "product"). Typed against CustomFieldsRegistry. |
link | string | string[] | Module link(s) fetched alongside the entity; their data is available to columns and displays via the +link.* fetch. |
forms | CustomFormEntry[] | Fields injected into built-in create/edit/onboarding forms. |
displays | CustomDisplayEntry[] | Field replace/remove/add + ActionMenu actions on detail sections. |
list | CustomListExtension | Columns, bulk actions, filters, view defaults on the model’s list table. |
forms[] — form fields
CustomFormField:
| Field | Type | Description |
|---|---|---|
validation | Zod schema | Drives both the default input type and validation. |
defaultValue | unknown | ((data) => unknown) | Static value or a resolver from the loaded entity. |
label | string (optional) | Field label. |
description | string (optional) | Help text below the input. |
placeholder | string (optional) | Input placeholder. |
component | ComponentType (optional) | Custom render; falls back to a default input for the Zod type. |
displays[] — detail sections
fields[] is CustomDisplayField:
| Field | Type | Description |
|---|---|---|
id | displayFieldIds | (string & {}) | Built-in field id (autocompletes) → replace/remove; any other string → add a new row. |
component | ComponentType<{ data? }> | null | Render component, or null to remove a built-in field. |
component props:
| Prop | Type | Description |
|---|---|---|
data | unknown | The loaded detail entity, including any linked module data. If the config declares link: "brand", read it off data.brand here. |
actions[] is SectionAction (the same shape as list bulkActions):
| Field | Type | Description |
|---|---|---|
rank | number (optional) | Position within the section’s ActionMenu. |
component | ComponentType<{ data? }> | Owns its own label, icon, group placement, and onClick. |
component props:
| Prop | Type | Description |
|---|---|---|
data | unknown | The loaded detail entity, including any linked module data (e.g. data.brand). |
list — list table
columns[] is CustomColumn:
| Field | Type | Description |
|---|---|---|
id | string | Column id — matches a built-in column to override, or adds a new one. |
header | string (optional) | Column header text (for added columns). |
component | ComponentType<{ row?, value? }> (optional) | Cell renderer. |
component props:
| Prop | Type | Description |
|---|---|---|
row | unknown | The full row entity (including linked module data). |
value | unknown | The cell value for this column id, when derivable. |
bulkActions[] is SectionAction ({ rank?, component }, component props { data? }).
Bulk-action rendering is deferred in the MVP.
bulkActions are accepted and
surfaced by the config but not yet mounted into the list toolbar.createFormHelper
createFormHelper<T>() returns Medusa’s zod surface for describing field
validation and value types:
validation types the value. The two compose — codegen types the address,
Zod types the payload.
Persistence
Related
Extend forms and tables
Step-by-step build with
defineCustomFieldsConfig.Add a widget
Inject a component into a zone.
Customize navigation
Reorder and hide sidebar items.
Custom Fields module
The backend storage layer.