Record Edit Modes
Choose between modal and full-page record forms with the editMode metadata flag.
Record Edit Modes
ObjectUI's default console shell (@object-ui/app-shell) supports two ways
to render the create/edit form for a record:
- Modal (default) — the form opens in an overlay dialog above the current view. Best for short forms, quick edits, and contextual data entry.
- Page — the form takes over a full route. Best for long forms, multi-tab or wizard layouts, or anywhere you need a deep-linkable URL that survives a refresh and integrates with the browser back button.
Both modes use the same <ObjectForm> pipeline under the hood, so all
field types, sections, validations, and visibility expressions work
identically in either mode.
Choosing a mode
Set editMode on the object metadata:
// metadata/objects/account.json
{
"name": "account",
"label": "Account",
"editMode": "page", // "modal" (default) | "page"
"fields": {
"name": { "type": "text", "label": "Name", "required": true },
"industry": { "type": "picklist", "label": "Industry" },
"owner": { "type": "lookup", "label": "Owner", "reference_to": "user" }
}
}Omitting editMode (or setting it to "modal") keeps the existing
behavior — clicking Create or Edit opens the global ModalForm
overlay.
URL patterns
When editMode: "page" is set, the console renders the form on a
dedicated route under the active app:
| Action | URL |
|---|---|
| Create | /apps/:appName/:objectName/new |
| Edit | /apps/:appName/:objectName/record/:recordId/edit |
Examples (for an app sales and an object account):
- Create:
https://your-console.example/apps/sales/account/new - Edit:
https://your-console.example/apps/sales/account/record/0015e000abcd/edit
These URLs are stable. Users can bookmark them, share them in chat, or
refresh the page mid-edit (the form rehydrates from the URL :recordId).
Triggering the routes from JSON
In addition to the implicit "click create/edit on a list" entry point,
two declarative actions let you open the page-mode routes from an
action:button in metadata. The handler name goes in actionType: that
is the key the button renderer forwards to the action runner as the
action's type, and the runner dispatches to the handler registered under
it. Arguments go in a top-level params object:
{
"type": "action:button",
"label": "New Account",
"icon": "plus",
"actionType": "navigate_create",
"params": { "objectName": "account" }
}navigate_edit additionally needs the record to open. params reaches
the handler verbatim: template expressions such as ${record.id} are not
evaluated inside params, and action:button does not inject the
surrounding row, so a declared navigate_edit button carries a literal
recordId:
{
"type": "action:button",
"label": "Edit",
"icon": "pencil",
"actionType": "navigate_edit",
"params": {
"objectName": "account",
"recordId": "0015e000abcd"
}
}For a per-row Edit that follows the record under the cursor, use the
list or detail view's built-in Edit entry point instead: under
editMode: "page" it already routes to the same URL (see Migrating an
existing object below).
When invoked from inside an ObjectView, the action context already
carries the active objectName, so params may be omitted entirely:
{
"type": "action:button",
"label": "New",
"actionType": "navigate_create"
}Behavior summary
| Aspect | Modal | Page |
|---|---|---|
| Default | ✅ | — |
| Deep-linkable URL | ❌ | ✅ |
| Survives refresh | ❌ | ✅ |
| Back button closes form | n/a | ✅ |
| Best for | quick edits | long / multi-section forms |
Migrating an existing object
The change is additive — existing apps continue to work unchanged. To migrate a single object to page mode:
- Add
"editMode": "page"to the object metadata. - (Optional) Adjust the form layout — page mode pairs well with
formType: "tabbed"orformType: "wizard"for long forms. - Reload the console. Existing Create / Edit entry points automatically route to the new pages; no UI code changes required.