Configure list/read/create/update Views
The built-in CRUD views render from Model Info, so a registered model starts with working list, read, create, and update pages. When one view needs different fields, actions, or list controls, you change it through Model Config overrides. Views that you do not override keep the defaults.
This guide assumes a working CRUD surface: the model is registered with a viewset, and makeCRUDRoutes wires its routes (see Create a CRUD Surface). Keep the model's model-info response at hand, so you can check which fields, actions, and expands the server lists. Contract-First Dynamic UI describes how model config ranks against component props and server defaults.
Baseline Config from Model Info
storeModelConfig builds each view's config from model info. You override only the keys that need to change. The defaults are:
- The View Field Lists,
displayFieldsandfetchFields: every serializer field except the pk and fields markedhidden. Two views narrowdisplayFields:createshows only writable fields. A new record has no value for a read-only field, and the server ignores input for one.listshows only fields whose model-info entry does not setlist_defaulttofalse. A workflow model's serializers setlist_defaulttofalseonworkflow_state_codeandvalid_transitions(workflow.serializers.workflow_serializer_fields), so a workflow list showsworkflow_state_namealone.
submitFields: the writable fields. A reverse foreign key, such as an order'sitems, stays out unless the view'sexpandnames it. Without the expand, forms show the field read-only.expand: empty. A view requests an Expand only when its config names it, and building the config fails when a dotted field list entry names a relation that the view'sexpandomits.routeActionsandactions: every action in the user's Model Actions.actionDetails: each action's model-info entry, keyed by action name, including itsdetailandbulkflags.fieldDetails: each field's model-info entry, keyed by field name.filterables: the filters in model info'smodel_filtering.sortables: the field names inmodel_ordering.fields.sorted: the server's default order frommodel_ordering.default. A descending field carries a leading-.showTotalRecordNumistrue, andallowColumnHidingisfalse.actionRedirects:{ default: "update" }when the user can update. Otherwise,defaultis"read"when the user can read,"list"when the user can list, andnullwhen the user can do neither.
Register Overrides with setConfig
Call setConfig when the application starts, before any view of the model renders. It takes the model, a model-wide override, and a map of per-view overrides:
import { storeModelConfig } from "@vueda/stores/storeModelConfig.js";
storeModelConfig().setConfig(
{ app: "myapp", model: "widget" },
// Model-wide overrides (every view)
{
displayFields: ["name", "status", "category"],
fetchFields: ["name", "status", "category", "description"],
submitFields: ["name", "status", "category", "description"],
expand: ["category"],
},
// Per-view overrides
{
create: { submitFields: ["name", "category"] },
list: { displayFields: ["name", "status"] },
},
);For the same key, the per-view override wins over the model-wide one. Key a per-view override by route name (list, read, create, update) or by action name. read and retrieve name the same view.
Each call replaces what it names. A model-wide object replaces the stored model-wide override, and {} clears it. Each per-view key replaces that view's stored override. Pass null for a level that you want to keep.
Choose Each View's Field Lists
Each view uses the three field lists this way:
- ViewList requests
fetchFieldsand renders a column for eachdisplayFieldsentry. It always adds the pk to the request. When you name onlydisplayFields, the list'sfetchFieldsfollow them. - ViewRead and ViewUpdate retrieve
fetchFieldsandexpandthrough useDetailView. They also request the object's Available Actions, and itsformatted_namefor the record name heading when the model info lists that field. Aformatted_namerequested only for the heading stays out of the form's values. - ViewCreate and
ViewUpdaterenderdisplayFields, and create derives its initial values from them. The form'sfieldsprop replaces the list.ViewUpdatetakesfieldsas its own prop, andViewCreatepasses it throughformProps.
A save uses all three lists:
displayFieldsselects the fields that the form renders.submitFieldsselects the values that the request body carries. A value outsidesubmitFieldsstays out of the body, even when the form shows it.fetchFieldsselects the fields that the save response returns, through Sparse Fields. Both views add the pk.
An update body also carries the pk of the object that the view edits. The update saves to that object, even when submitFields names the pk.
A top-level submitFields name, such as lines, sends that field's whole value, including nested inline rows. A dotted path, such as address.city, sends only that nested value. Do not name a subfield of an expanded relation: the view's config then fails to build, with an error that names the path.
For example, an order form can render and submit quantity while a summary reads the server-calculated unit_price and total:
storeModelConfig().setConfig(
{ app: "shop", model: "order" },
{
displayFields: ["quantity"],
fetchFields: ["quantity", "unit_price", "total"],
submitFields: ["quantity"],
},
);The save response is available as objectForm.state.object, and ViewUpdate retrieves the object again after each save. The summary shows fresh values after every save, and neither unit_price nor total joins the next request body.
The demo below runs that configuration against an offline server. Change the quantity and submit, then compare the request log with the summary.
Create and update resolve submitFields and fetchFields in this order:
- The view's
submitFieldsorfetchFieldsprop, when it is not empty. - The per-view override.
- The model-wide override.
- The
fieldsshorthand from either override, which sets all three lists at once. - The defaults from model info.
An omitted list falls through to the next step. An empty list in a per-view override skips the model-wide list and falls through to the fields shorthand, then the defaults.
Ignored Field values stay out of the body even when submitFields names them. submitFields narrows only what the client sends. The server still validates each write against the serializer's writable fields and the user's permissions.
When expand is not empty, the config adds each expanded relation's subfields to fieldDetails under dotted keys. With category expanded, fieldDetails["category.name"] holds the metadata for the category's name, and you can override it there.
Show a Field the Defaults Leave Out
To show a read-only field on a create form, or a list_default: false field in a list, name the view's fields:
storeModelConfig().setConfig({ app: "myapp", model: "purchaseorder" }, null, {
create: { displayFields: ["reference", "supplier", "total_value"] },
list: { displayFields: ["reference", "workflow_state_code", "workflow_state_name"] },
});The narrower create and list defaults apply only when no override names that list or the fields shorthand. A model-wide displayFields reaches create and list unchanged.
To change a field's list default wherever a serializer is used, set the flag in the serializer field's style. style={"list_default": False} leaves the field out of the default list. style={"list_default": True} keeps a field that WorkflowFieldsSerializerMixin would leave out.
Choose Actions and Routes
Three keys control actions. Name actions in each by Canonical Action Name, such as retrieve for the read route.
Limit Which Routes Open
routeActions narrows which actions Route Admission lets a route open. Set it in the model-wide override, because the route guard reads only the model-wide config:
storeModelConfig().setConfig({ app: "myapp", model: "widget" }, { routeActions: ["list", "retrieve", "update"] });A route for a model action that routeActions leaves out shows an "Action Not Found" toast and redirects to the makeCRUDRoutes actionRedirect. A workflow transition code still opens, whatever routeActions lists. Routing and View Resolution Model describes the full guard chain.
Limit Which Buttons Render
actions narrows which action buttons the views render. An action left out of actions can still open by URL, so use routeActions to close the route as well.
actions takes either a list of action names or an object (ActionPermissionConfig) that maps each action to true or to a list of group names. With the object form, a user sees an action's button when its value is true or when the user belongs to one of the listed groups:
storeModelConfig().setConfig({ app: "myapp", model: "widget" }, null, {
list: { actions: { create: true, destroy: ["managers"] } },
});Group membership here only hides buttons. The server still checks each request's permissions.
Check How Each Action Is Grouped
actionDetails decides where an action's button renders. Model info supplies an entry for each action that it lists, and a view renders no button for an action without an entry. The detail and bulk flags group the buttons:
ViewListrenders abulkaction in the selection strip, and an action with neither flag as a button in the page title. It renders no button for adetailaction that is notbulk. Link List Rows to Read and Update Views sets up navigation from a row to its read or update view.- The read and update views render a
detailaction only when the object'savailable_actionsincludes it. They render actions withoutdetailfrom the model's action list. ViewCreaterenders only actions withoutdetail.
LinkModelView requires a pk before it enables a link to a detail or bulk action, or to a workflow transition code. If you override actionDetails, keep these flags matching the server's action metadata.
Choose the Redirect After a Save
After a successful save, ViewCreate and ViewUpdate go where their redirectAfter prop sends them:
ViewCreateredirectAfterdefaults to"update", which opens the new record's update view. It also accepts"read"and"list".ViewUpdateredirectAfterdefaults tonull, which stays on the page. It also accepts"read"and"list".
actionRedirects sets the Action Redirect for views that run through ModelActionForm, such as destroy, extra actions, and workflow transitions. Create and update do not read it. Design Transition UX and Redirects describes how an action form picks its redirect.
Set List Controls
These keys and ViewList props shape the list:
filterablessets which filters the filter menu offers. TheViewListfilterablesprop replaces it.sortablessets which columns offer sorting. Filtering and Ordering Semantics describes which fields the server accepts for ordering.sortedsets the sort that the list opens with. A saved sort preference wins over it, and SortGroup's "Reset sort" control returns to it.showTotalRecordNumshows the total record count in the pagination bar.allowColumnHidingshows a control that lets the user hide columns. TheViewListprops of the same names override both.- The
ViewListpropspageSizeOptionsanddefaultPageSizeset the rows-per-page choices and the starting size. Include"all"inpageSizeOptionsto let users load every row, or setdefaultPageSizeto"all"to start there.
List Preferences
ViewList saves a user's list choices in the browser's local storage, one entry per app and model. It saves:
- the columns the user hid,
- the sort the user chose,
- the page size,
- the filters the user added, and the search term.
Preferences belong to the browser. Everyone who signs in on that browser shares them, and signing out does not clear them. To clear a model's entry, call clearPreferences.
Your config and props set the starting state, and a saved preference wins over them. The list restores hidden columns on every visit. It restores the sort, page size, filters, and search term only when it opens with no query parameters. It restores a saved page size only when pageSizeOptions still offers it, and a saved filter only when the list still offers that filter.
A hidden filter's URL value, such as an ?id=1,2 link, stays in the URL for that visit and is never saved. Query parameters that the list does not use are not saved either. Open a Scoped List describes which filters params supplies and keeps out of preferences.
Verify the Result
To read the resolved config in a component, call useModelConfig with the app, model, and view name:
import { useModelConfig } from "@vueda/use/useModelConfig.js";
const modelConfig = useModelConfig(toRef(props, "app"), toRef(props, "model"), "list");
// modelConfig.config: the resolved config for the list view
// modelConfig.info: the model info from the server
// modelConfig.loading, modelConfig.errored, modelConfig.error: load stateThen check each view in the browser:
- The list renders a column for each
displayFieldsentry, and its request'sfcarries thefetchFieldsplus the pk. - The read view renders the expected fields, including expanded subfields when
expandnames the relation. - The create and update forms render the view's
displayFields. A create body carries only thesubmitFieldsvalues. An update body carries thesubmitFieldsvalues and the pk, and its URL names the edited object. - After a save, create and update go where their
redirectAftersends them. - Each view shows the action buttons that
actionsandactionDetailsallow. - A route for an action outside
routeActionsshows "Action Not Found" and redirects. - The filter menu and sort controls match
filterablesandsortables. - After you hide a column and reload the list without query parameters, the column stays hidden.
Troubleshooting
A list column shows no values. The list override sets fetchFields and leaves the field out. Add the field to fetchFields, or remove fetchFields from the list override so that the list's fetchFields follow displayFields.
An action button is missing. Check in this order:
- The model-info response lists the action in
model_actions. If not, the user lacks the permission for it. - The view's
actionsincludes the action. With the object form, the user belongs to one of the action's groups. - On a read or update view, the object's
available_actionsincludes adetailaction. - The action's
actionDetailsflags put it in a group the view renders. A list renders no button for adetailaction that is notbulk.
A route shows "Action Not Found". routeActions leaves the action out. Set routeActions in the model-wide override, and use retrieve for the read route.
A create request fails validation for a required field. submitFields leaves the field out, so its value never reaches the server. Add the field to submitFields, or give it a default on the server.
An expanded subfield has no fieldDetails entry. The config adds dotted subfield keys only when expand names the relation. Add the relation to expand for that view.