Custom zones · 0.8.1

Admin: custom cells, forms and complete pages

Choose an in-place extension or replace a generated page.

On this page

Extend the generated interface in place or replace an entire page. In-place extensions retain the generated list and form; a full replacement gives your team responsibility for the page and its behavior.

ENTITY_NAME is the entity directory, such as product. EntityName in an export is the PascalCase entity name, such as Product. Open the files generated for your entity and use their actual names.

Extension files

File What belongs there
admin/src/features/ENTITY_NAME/custom/ui-extension.tsx Cells, extra columns, row actions and controls inside generated forms
admin/src/features/ENTITY_NAME/custom/extension.tsx Titles, actions, data transformations and full PageOverride exports
admin/src/features/ENTITY_NAME/generated/ui-extension.tsx Generated extension contract to read
admin/custom/vite.ts Supported additional Vite configuration

Extend a list

useEntityNameListUI(context) receives current records, loading/error state and generated columns. Return the additions you need:

Property Purpose
columns Extra columns with unique IDs
cells Custom renderers for existing fields
rowActions Actions with an ID, label, isAllowed, handler and optional disabled condition
status Additional list status

Custom columns do not gain SQL filtering or sorting automatically. Do not reuse generated column IDs or reserved __actions, __customActions, __related. Fetch external values in a bounded batch for the current page's IDs and handle loading, errors and page changes.

Extend a form field

useEntityNameFormUI() returns form extensions. textFields supports the eligible ordinary visible editable string/text inputs; imageFields supports image controls when present in the model; wrappers wraps individual fields.

Keep the supplied inputProps, including name, required, disabled and the value: the generated form submits FormData. A wrapper must render defaultControl exactly once. Use the supplied onDirtyChange for custom state so the unsaved-changes warning stays accurate.

For localized controls, name identifies the model field, while inputName/inputProps.name is the actual HTML submission name for the selected locale. Do not derive it from a label such as name-ru. Preserve readonly/disabled behavior and server permissions, including demo inputs that allow typing but cannot save.

Replace a page completely

In custom/extension.tsx, find the existing EntityNameListPageOverride or EntityNameEditPageOverride. Replace null with your component:

function MyListPage() {
  return <section>Your page content</section>
}

export const EntityNameListPageOverride: ComponentType | null = MyListPage

Replace the existing declaration rather than adding a second one. The generated route selects your component instead of the generated page; it does not append the standard table below it. Do not render that table in your component unless you need it. Virtual entities provide EntityNameListPageOverride and EntityNameDetailsPageOverride.

A full replacement owns requests, saving, loading/error states, pagination and accessibility. Use cells for a changed cell instead of replacing the page. Custom UI does not bypass server authorization.

Build the admin with npm run build from admin/ and check the changed screen with the appropriate user permissions. See The custom development workflow for regeneration and compatibility.