Hooks
Frontend-core and orchestr expose Nuxt runtime hooks that let you extend or modify the complete behaviour of your Laioutr Frontend. Register hooks inside a Nuxt plugin for client-side hooks, or a Nitro plugin for server-side hooks.
Hook mechanics
Every hook uses one of four mechanics, which decide when your handler runs and how it shapes the result:
- Filter — runs after the default logic with
result.valuepre-seeded. Transform it, replace it, or leave it untouched. Chained across plugins: each handler receives the previous one's output. - Override — runs before the default with
result.valueempty. Set it to take over; leave it unset to fall back to the default. - Modify — mutates the payload object in place. There is no
resultslot. - Lifecycle — a
before/success/error/finallysequence around an operation.
Each hook also has a dispatch, shown on its card. Synchronous handlers run inline and are not awaited — set values immediately, since a returned promise is ignored. Asynchronous handlers may await. Dispatch is a property of the individual hook, not of its mechanic.
A throwing handler does not fail the caller
Every result-returning hook guards each handler on its own. One that throws is skipped with a warning, the remaining handlers still run, and the value keeps threading, so the caller falls back at worst to what it seeded. This matters most inside a render, where SSR has no error boundary: before, a handler an app installed could fail the page. A handler that mutates result.value in place and then throws keeps that mutation.
Implementing a result-returning hook of your own
getHookResult and useHookResult are auto-imported, so a package outside Frontend Core can offer this same pattern without restating the synchronous-caller trick that makes a handler's result.value readable on the next line:
export const useBadgeLabel = (product: Product) =>
useHookResult('acme:badge:label', { product }, defaultLabel(product));
Pass a seed and the hook behaves as a filter, each handler receiving the previous one's output. Omit it and it behaves as an override, with result.value starting undefined until a handler sets it. useHookResult reads the Nuxt app for you; getHookResult takes one explicitly, for code that already holds it.
Frontend Core Hooks
These hooks run on the client. Register them in a Nuxt plugin with nuxtApp.hook().
Link Resolver
Three hooks let you customize how linkResolver resolves links, switches locale paths, and switches market URLs.
Link Intent
Page Renderer
Page Head
Content Preview
Two hooks around content preview: one decides where the preview token comes from, the other tells you when preview turned on or off.
Analytics
Four hooks along the emission pipeline. Every event passes through the first three in order: :emit can veto it, :enrich shapes the whole event, and :project shapes each orchestr entity found in the payload. An event that waited for a consent decision passes through :redeliver as well, once, on its way out.
Redaction: global or per destination
:emit, :enrich and :project all run once per event, before the fan-out. Whatever they change, every destination sees. Use them for a change that belongs to the event itself.
To give one destination different data, use frontend-core:analytics:deliver. It fires per destination, immediately before that destination is called, and result.value starts as the event that destination would otherwise have received.
One destination is exempt: the transport that carries events to the project's own ingest endpoint. The recipient fan-out for server-side subscribers happens there, and those subscribers declare their own consent requirements, so a handler must not be able to reshape what they receive. A destination declares this role with stage: 'transport', which only Frontend Core's own transport sets.
A destination author can also shape their own output inside track(), since that code owns what it sends.
Redirects
Orchestr Client Hooks
These hooks fire during client-side action execution. All receive a token string that identifies the action (e.g. ecommerce/cart/add-items).
Fetch Action Hooks
Fetch action lifecycle
Four hooks fire around every fetchAction request. finally always runs, whether the action resolved or errored.
fetchActionuseFetchActionuseQueryActionuseMutationAction- before
orchestr:action:fetch:beforeBefore the request is sent.
{ token, input } - success
orchestr:action:fetch:successAfter the action resolves.
{ token, output } - error
orchestr:action:fetch:errorAfter the action rejects.
{ token, error } - finally
orchestr:action:fetch:finallyAlways
{ token, output?, error?, input }
Mutation Action Hooks
Mutation action lifecycle
useMutationAction fires these around the mutation.
useMutationAction- before
orchestr:action:mutation:beforeBefore the mutation runs.
{ token, input } - success
orchestr:action:mutation:successAfter the mutation resolves.
{ token, output, input, context } - error
orchestr:action:mutation:errorAfter the mutation rejects.
{ token, error, context } - finally
orchestr:action:mutation:finallyAlways
{ token, output?, error?, input, context }
The context value comes from Pinia Colada's mutation context and is set by the onMutate callback.
export default defineNuxtPlugin((nuxtApp) => {
// Track all failed actions (both fetch and mutation)
nuxtApp.hook('orchestr:action:fetch:error', ({ token, error }) => {
errorTracker.capture(error, { action: token, type: 'fetch' });
});
nuxtApp.hook('orchestr:action:mutation:error', ({ token, error }) => {
errorTracker.capture(error, { action: token, type: 'mutation' });
});
});
Action Request Hooks
Two hooks around each HTTP request that fetchAction sends. :prepare adds request headers, and :retry decides whether a failed request goes out once more. Bot protection uses both: :prepare attaches the provider's proof, and :retry sends a step-up proof after the server asks for one.
Unlike the fetch:* hooks, which fire once per fetchAction call, :prepare fires once per request. A call that is retried fires it twice.
URL Query Parameters
Two hooks control how Orchestr reads and writes URL query parameters (pagination, sorting, filters). See URL Query Parameters for the full reference with examples.
Client Environment
Orchestr Server Hooks
These hooks fire during server-side action handler execution. They are Nitro runtime hooks and must be registered in a Nitro plugin, not a Nuxt plugin.
Server handler lifecycle
Four hooks fire around the server-side action handler. Register them in a Nitro plugin with nitroApp.hooks.hook().
- before
orchestr:action:handler:beforeBefore the handler runs.
{ token, input, clientEnv } - success
orchestr:action:handler:successAfter the handler resolves.
{ token, output } - error
orchestr:action:handler:errorAfter the handler throws.
{ token, error } - finally
orchestr:action:handler:finallyAlways
{ token, output?, error?, input }
A request whose body fails validation fires :error and :finally too, with input undefined, and reaches the client as Action failed with status 400 and the validation error as data.
export default defineNitroPlugin((nitroApp) => {
const pending = new Map<string, number>();
nitroApp.hooks.hook('orchestr:action:handler:before', ({ token }) => {
pending.set(token, Date.now());
});
nitroApp.hooks.hook('orchestr:action:handler:error', ({ token, error }) => {
const startedAt = pending.get(token);
const duration = startedAt ? Date.now() - startedAt : undefined;
console.error(`[orchestr] ${token} failed after ${duration}ms`, error);
pending.delete(token);
});
});
Environments & Staging
How to preview changes before going live, using Vercel preview deployments and workarounds for environment-specific content.
Media and Media Library
Laioutr’s media library abstraction lets business users choose assets from connected backends visually in Cockpit. Implement a media library connector for your asset system so editors can browse, filter, and (optionally) upload media in Studio.