Page context
These composables let your layer's Vue read where it sits in the dashboard — the navigation tree, the current module, whether the viewer owns the tenant — and persist per-user UX preferences. The state-bearing ones are shared, SSR-safe singletons, so every component sees the same values.
Layout & module context
The dashboard fetches its navigation and module map once into shared state; these composables expose that state and derive the current position from the route.
useSiteLayout()
useSiteLayout exposes the dashboard's loaded structure as a bag of shared reactive refs. Layer authors read these — most often modules, isOwner, and quickActions:
| Ref | Type | Holds |
|---|---|---|
siteLayout | SiteLayout | undefined | flat { pages, categories } keyed by slug |
siteLayoutTree | SiteLayoutTree | undefined | the nested nav tree (children + ordering) |
quickActions | QuickActionsPayload | undefined | { categories, actions } for the command palette |
modules | Record<string, ModuleInfo & { hasAccess: boolean }> | undefined | every registered module by id |
isOwner | boolean | viewer owns the current tenant |
pageLayouts | Record<string, PageLayout> | per-slug component-layout cache |
isLoading | boolean | a fetch is in flight |
loadingError | string | null | last load error message |
siteLayout.categories holds the categories that own a URL. A category declared
with urlSlug: "/" adds no segment of its own — it is a section heading, not a
route — so it is absent there and reachable through siteLayoutTree, which is
keyed by fullId.
const { modules, isOwner, quickActions } = useSiteLayout();
const myModule = computed(() => modules.value?.["my-module"]);
loadSiteLayout, loadPageLayout, refresh, refreshIfStale, findMatchingRoute, findMatchingRouteOrCategory, and the dev-only probeAndCommitRoute. These are internal framework plumbing (the page renderer, middleware, and dev hot-reload plugin call them). Treat the refs above as your read surface and leave the methods alone.useCurrentModule()
() → ComputedRef<{ id, info } | null> — the module owning the current route, derived from a /modules/<id>/… path and looked up in modules. Returns null off any module page. info is the ModuleInfo & { hasAccess } entry.
const current = useCurrentModule(); // current.value?.id
useIsOwner()
() → ComputedRef<boolean> — convenience wrapper over useSiteLayout().isOwner; true only when the viewer owns the tenant. Use it to gate owner-only UI. See Auth & permissions.
useModulesListing()
() → { modules, isLoading, loadingError, loadModulesListing, refresh } — owner-only fetch of /cms/modules-listing (each entry adds a landingSlug). Call loadModulesListing() to populate modules; a 403 is caught and surfaced as a friendly loadingError ("Forbidden: only owners can list modules") rather than thrown. For the management/marketplace surface, not per-page rendering.
useModuleHistory()
() → { record(moduleId, path), getLast(moduleId), entries } — remembers the last path visited inside each module (in-memory shared state) so navigation can resume where the user left off. getLast returns the stored path or null; entries is a computed list of { moduleId, path } ordered oldest to newest visit — recording an already-known module moves it to the end, so the ordering reflects true recency (the command palette's "recent modules" group reads it reversed).
const history = useModuleHistory();
history.record("crm", "/modules/crm/contacts/42");
const back = history.getLast("crm"); // "/modules/crm/contacts/42"
const recent = history.entries.value; // [{ moduleId: "crm", path: "…" }, …]
useQuickActions()
() → { categoryGroups: ComputedRef<QuickActionCategoryGroup[]> } — the quick actions of the current user (from useSiteLayout().quickActions), grouped by category and sorted by order on both levels. The payload only ever carries actions the caller can run — the server leaves the others out — so there is no access flag to filter on. It is the shared source behind every quick-action surface, the header popover and the command palette, so read it instead of re-deriving groups from the raw payload. The module also exports dispatchQuickActionTarget(target), which runs a quick action's client-side behavior: navigate and openForm route to the action's page (openForm carrying the intent in the query, which the table view picks up when it mounts), and event dispatches a window CustomEvent in place.
const { categoryGroups } = useQuickActions();
// categoryGroups.value → [{ category, actions }, …], permission-filtered
dispatchQuickActionTarget(action.target); // run one
Page setup functions (setupId)
A backend page can name a setup function in its setupId option (Pages & components). The renderer resolves that id from the function registry when the page mounts, so your layer registers the handler once, from a plugin:
export default defineNuxtPlugin(() => {
const { registerFunction } = useDefinedFunctions();
registerFunction("app.pages.DashboardSetup", (context: PageSetupContext) => {
// React to any component on the page:
const off = context.on("form", FormEvents.FIELD_CHANGE, (data) => {
context.emit("chart", "app.chart.Highlight", data);
});
return () => off(); // cleanup — runs on page unmount
});
});
PageSetupContext carries pageInfo (the resolved page), permissions (the viewer's set), and the on / emit pair — a page-scoped view of the Component events. on returns an unsubscribe function; the setup function may return a cleanup callback (or a promise of one), which the renderer calls on unmount.
Use a setup function when a page needs orchestration across components but no custom rendering — it keeps the page fully backend-described.
User preferences & UX
These composables persist per-user settings in cookies, so they survive reloads and are available during SSR.
usePreferences()
usePreferences is a cookie-backed key/value store (user-preferences cookie, one-year max-age) addressed by dotted paths. It returns:
| Member | Purpose |
|---|---|
preferences | readonly snapshot of the whole tree |
getPreference(keyPath, default?) | read a nested value, falling back to default |
setPreference(keyPath, value) | write a value, creating intermediate objects |
updatePreference(keyPath, partial) | shallow-merge into an existing object value |
removePreference(keyPath) | delete a key |
clearAllPreferences() | reset to {} |
const { getPreference, setPreference } = usePreferences();
setPreference("tables.contacts.pageSize", 50);
const size = getPreference("tables.contacts.pageSize", 25);
useInterfaceScale()
() → CookieRef<InterfaceScale> — UI density as a writable cookie ref (cms-interface-scale, values "small" | "normal" | "large", default "normal"). Assign to .value to change it.
useFavoritePages()
useFavoritePages tracks the user's pinned pages, persisted to the cms-favorite-pages cookie and mirrored in shared state. A FavoritePage records the page's id, path, title, and optional icon. It returns:
| Member | Purpose |
|---|---|
favorites / sortedFavorites | raw list / title-sorted computed list |
isFavorite(path) | is this path pinned |
toggleFavorite(page) | add/remove, returns the new pinned state |
addFavorite(page) / removeFavorite(path) | explicit add / remove |
clearFavorites() | remove all |
cleanupInvalidFavorites(isValidPath) | drop entries failing the predicate |
const { isFavorite, toggleFavorite } = useFavoritePages();
toggleFavorite({ id: "p1", path: route.path, title: "Contacts", icon: "i-ph-users" });
App config & system
useHomepage()
() → string — the dashboard's home path, from the homepage config key (Configuration), defaulting to "/". Use it for "back to home" links instead of hard-coding /.
useFirstAccessiblePagePath()
() → string | undefined — the path of the first page the current user can access, in tree (sidebar) order. Categories the user cannot access are still descended into (they may hold accessible pages), but only navigable, non-hidden pages the user can access are returned; settings pages are a last resort and module pages are never chosen (they have their own routing). The page renderer uses it to redirect a user whose role cannot access the configured homepage; undefined when the site layout is not loaded or no page is accessible. A pure firstAccessiblePagePath(tree) variant takes the layout tree directly, for use after an await where composable context is unavailable.
useSystemState()
Exposes the instance's system state, fetched server-side so it is already available during SSR. It returns:
systemState— thehas_onboardedflag (what the onboarding gating reads) and the dashboardmeta(title,description) from the backend config.metaTitle/metaDescription— computed conveniences for a page's<head>.error/refresh()— the fetch error, and a refetch to call after a state change.