CONSOLE SETTINGS (the store's admin console — NOT the public site): two documents the store's members see in the console, both SHARED by Demo and PROD (a write from either environment changes both). Read them with readConsoleSettings; write them with writeConsoleMenu / writeConsoleOverview. Both writes REPLACE the whole document — read first, change what was asked, write the FULL list back (a group or widget you leave out is deleted). Keep the ids of existing rows; omit id on new rows (it is minted).
PINNED MENU — custom groups the console shows UNDER its fixed areas (Home, Studio, Content, Shop, SEO, Modules, Settings); every member sees them, each item filtered by that member's own access. A group is {id?, label, icon?, items} (≤ 12 groups, ≤ 24 items, labels ≤ 60 characters, plain text — not i18n keys; icon a Material Symbols name, default the pin). Item kinds:
item{page} — a console page.pageMUST be one of readConsoleSettings.pages, copied verbatim ('' = Overview, "orders", "products", "seo/urls", "settings/payments", …) — never guess or shorten a route.filter(string fields, e.g. {"status": "public", "tags": "sale"}) only with page "products".collection{collection, filter?} — a data collection's listing; the collection must be registered (listCollections / registerCollection).filtere.g. {"locale": "cs_CZ", "status": "public"}.custom{custom} — a custom collection of the Data page (a saved view: picked documents and previewed collections — an id from listCollectionscustom).document{collection, doc} — one data document's editor (collection= the collection key,doc= its id).form{form} — a form's submissions.setup{doc} — a setup document (web,design, …).module{module, page?} — an installed module's admin page (page= the manifest's admin.pages id; '' = the module's detail page). Optional on every item:label(default: the page / entity name),icon(a Material Symbols name: "restaurant", "local_shipping"). Example: {"groups": [{"label": "Kitchen", "items": [{"kind": "collection", "collection": "recipes", "icon": "restaurant"}, {"kind": "form", "form": "contact"}, {"kind": "item", "page": "orders"}]}]}
A GOOD MENU — what to propose (a build ends by proposing it; an audit checks it — readRule("audit-website") M): one group per BUSINESS CONTEXT, labelled in the client's language (the same names as the Data page groups — readRule("data-collections")); in it the content collections editors maintain, the SINGLETON documents they edit (header, footer, homepage, contact page) as document items, every active form as a form item, saved views as custom items and the module pages they operate. Never the raw pages / components collections as the only entry — pin the documents in them. Ask before writing (writeConsoleMenu replaces the whole menu).
OVERVIEW — the dashboard widgets, in display order (≤ 24), and days (the shared metric window: 7, 30 or 90). A widget is {id?, kind, label?, size?: "tile"|"half"|"full", …}; the kind's discriminator field must take one of the values readConsoleSettings.widgets lists:
tile{metric} /chart{metric} — commerce numbers (revenue, orders, invoices; readyOrders / topProducts).ga4-tile{metric} /ga4-chart{report} — Google Analytics (needs GA4 configured on the store).stats-tile{metric} /stats-chart{chart} — platform traffic, zero setup.ready-orders,recent-orders,activity,top-pages— lists, optionallimit1–50.form-records{form, status?, limit?} — a form's latest submissions.storage,credits,visits,visits-chart,assistant— no options.module{module, widget} — an installed module's dashboard widget (the manifest's admin.widgets id). Example: {"widgets": [{"kind": "tile", "metric": "orders"}, {"kind": "stats-chart", "chart": "traffic", "size": "half"}, {"kind": "activity", "limit": 10}], "days": 30}
NOT console settings: a data collection's listing COLUMNS are registerCollection {key, columns}; the Products / Orders / Coupons listing columns are not configurable. A refusal (errors) names the row — fix that row and write again; warnings (an unregistered collection, a missing form or module page) let the write through — tell the user. Writing needs the owner role or a token with the project.console grant; a role_forbidden answer means the token predates it (the user adds the console grant under Settings › Advanced › SDK & MCP — the token value stays the same) or, for a user's personal token, that they are not a manager of the project. Tell the user; do not retry.