Features

Hooks

Extend frontend-core and orchestr behavior using Nuxt runtime 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.value pre-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.value empty. Set it to take over; leave it unset to fall back to the default.
  • Modify — mutates the payload object in place. There is no result slot.
  • Lifecycle — a before / success / error / finally sequence 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.

Frontend Core Hooks

These hooks run on the client. Register them in a Nuxt plugin with nuxtApp.hook().

Three hooks let you customize how linkResolver resolves links, switches locale paths, and switches market URLs.

frontend-core:link-resolver:resolve
Transform, replace, or pass through a resolved link before it's used — for example, point product references at an external catalog.

When it fires: After every call to linkResolver.resolve(), once the default resolution has produced a value.

Runs onClientRegister inNuxt pluginDispatchSynchronous
link

The link being resolved.

result
{ value: string }

Mutate result.value to transform the output. It arrives pre-seeded with the resolved URL or path — transform it, replace it, or leave it untouched. Threads across plugins, so each handler receives the previous output.

Switch locale path

frontend-core:link-resolver:switch-locale-path
Take over how the current page's URL is rebuilt when switching to another language, including cases the default can't resolve.

When it fires: Before the default logic, when switching the current page to another language.

Runs onClientRegister inNuxt pluginDispatchSynchronous
targetLanguageId
string

The language being switched to.

result
{ value?: string }

Set result.value to take over locale switching, including cases the default cannot resolve. It starts empty; leave it unset to fall back to the default. The first handler to set a value wins.

Switch market URL

frontend-core:link-resolver:switch-market-url
Take over the URL used when switching to a different market, which may include a change of host.

When it fires: Before the default logic, when switching to a different market.

Runs onClientRegister inNuxt pluginDispatchSynchronous
targetMarketId
string

The market being switched to.

targetLanguageId
stringoptional

The target language

result
{ value?: string }

Set result.value to take over market switching, which may include a host change. It starts empty; leave it unset to fall back to the default.

Page Renderer

Select page variant

frontend-core:page-renderer:select-page-variant
Choose which variant of a page is rendered — for A/B testing, personalization, or conditional layouts.

When it fires: When the PageRenderer component selects a variant.

Runs onClientRegister inNuxt pluginDispatchSynchronous
page
RenderPage

Contains id, type, path, and a variants array.

result
{ value?: RenderPageVariant }

Set result.value to a variant to render it instead of the default. It starts empty; leave it unset to keep the default.

Resolve page head

frontend-core:page-head:resolve
Read and rewrite the SEO and locale tags Frontend Core writes to the document head on every page.

When it fires: When the PageRenderer applies the page head via useHead, on every page.

Runs onClientRegister inNuxt pluginDispatchSynchronous
page
RenderPage

The page being rendered.

pageVariant
RenderPageVariant

The selected variant.

metaPage
MetaPage

The page's SEO meta.

currentDomain
stringoptional

The resolved market domain — undefined in Studio preview.

result
{ value: { seo, locale } }

Mutate result.value to change the head; it is pre-seeded with the computed head. seo is a flat useSeoMeta object (title, description, robots, og:/twitter:); locale is { htmlAttrs, meta, link } — html lang, og:locale, canonical, and hreflang alternates.

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.

Resolve preview token

frontend-core:content-preview:resolve-token
Take over where the content-preview token comes from — a CMS cookie, a header injected by a gateway — instead of the query parameter.Your plugin must use enforce: 'pre': frontend-core evaluates the token source during its own plugin setup, so a handler registered later is never consulted. Handlers must also be synchronous — you may read cookies or useRequestHeaders(), but you must not await.

When it fires: Before frontend-core reads the preview_token query parameter, on every evaluation of the preview token source.

Runs onClientRegister inNuxt pluginDispatchSynchronous
route
RouteLocation

The current route.

result
{ value?: string }

Set result.value to a token to drive content preview from your own source. It starts empty; leave it unset to fall back to the ?preview_token= query parameter. The first handler to set a value wins.

Content preview changed

frontend-core:content-preview:changed
Invalidate your own caches when content preview turns on or off. Fire-and-forget: there is no result slot, and a returned promise is ignored.Frontend Core already calls refreshNuxtData() and invalidateOrchestrQueries() for you, so Nuxt's own data and every stored orchestr query result are handled. Use this hook for anything else you hold — a hydrated Pinia store, a memoized CMS client.

When it fires: After a content-preview transition — entering preview, leaving preview, or swapping to a different token.

Runs onClientRegister inNuxt pluginDispatchSynchronous
enabled
boolean

The new state — true once the server has verified the token

Analytics

Three hooks along the emission pipeline. Every event passes through them in order: :emit can veto it, :enrich shapes the whole event, and :project shapes each orchestr entity found in the payload.

Emit analytics event

frontend-core:analytics:emit
Veto or pre-transform an event. Setting result.value to null drops it entirely — no enrichment, no buffering, no destination. Frontend Core registers no handlers here, so it is yours alone.

When it fires: At the start of every track() call, before enrichment, buffering and delivery.

Runs onClientRegister inNuxt pluginDispatchSynchronous
result
{ value: AnalyticsEvent | null }

Pre-seeded with the event as tracked. Transform it, or set it to null to drop the event before anything else sees it.

Enrich analytics event

frontend-core:analytics:enrich
Add to or rewrite the whole event — extra payload fields, a context of your own, a redacted value.Frontend Core registers two handlers here, in this order: it projects the orchestr entities in the payload (firing :project once per entity), then attaches the registered ambient contexts. Handlers run in registration order, so a handler that needs to see — or redact — a projected entity or an attached context must run after core's. That means enforce: 'post' or a dependsOn on the frontend-core plugin, not enforce: 'pre'.Redaction here is durable: :enrich runs before the event enters the replay buffer, so the redacted form is what gets buffered. The unredacted event is never retained and never replayed.

When it fires: After the :emit veto and before the event enters the replay buffer, on every event that survives.

Runs onClientRegister inNuxt pluginDispatchSynchronous
result
{ value: AnalyticsEvent }

Pre-seeded with the vetted event. Core handlers project payload entities and attach registered contexts before yours run; reassign result.value to change what is buffered and delivered.

Project analytics entity

frontend-core:analytics:project
Shape the wire snapshot a single entity turns into. Use augmentProjection for a typed handler bound to one entity type.

When it fires: Once per orchestr entity in the event payload, during :enrich.

Runs onClientRegister inNuxt pluginDispatchSynchronous
entity
ClientEntity

The orchestr entity found in the payload

overrides
Record<string, unknown>

The literal fields written alongside the entity at the track() call site, e.g. quantity.

result
{ value: unknown }

Pre-seeded with the base projection for the entity type, or { id } when no projector is registered for it. Reassign to add, remove or replace fields.

Redaction is global, not per destination

The bus hands the same event object to every eligible destination. There is no seam between enrichment and an individual destination, so an app cannot give one destination a redacted payload and another the full one — whatever you change in :enrich or :project, every destination sees.

A destination author can of course shape their own output inside track(), since that code owns what it sends. What you cannot do from outside is redact someone else's destination. If two destinations need genuinely different data, that difference has to live in the destinations themselves.

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.

Fired byfetchActionuseFetchActionuseQueryActionuseMutationAction
  1. beforeorchestr:action:fetch:before

    Before the request is sent.

    { token, input }
  2. successorchestr:action:fetch:success

    After the action resolves.

    { token, output }
  3. errororchestr:action:fetch:error

    After the action rejects.

    { token, error }
  4. finallyorchestr:action:fetch:finally

    Always

    { token, output?, error?, input }

Mutation Action Hooks

Mutation action lifecycle

useMutationAction fires these around the mutation.

Fired byuseMutationAction
  1. beforeorchestr:action:mutation:before

    Before the mutation runs.

    { token, input }
  2. successorchestr:action:mutation:success

    After the mutation resolves.

    { token, output, input, context }
  3. errororchestr:action:mutation:error

    After the mutation rejects.

    { token, error, context }
  4. finallyorchestr:action:mutation:finally

    Always

    { token, output?, error?, input, context }

The context value comes from Pinia Colada's mutation context and is set by the onMutate callback.

app/plugins/action-error-tracking.ts
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' });
  });
});

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.

Parse query params

orchestr:query-params:parsed
Adjust the parsed URL query params before Orchestr reads pagination, sort, and filter from them.

When it fires: After parsing the URL, before reading pagination, sort, and filter.

Runs onClientRegister inNuxt pluginDispatchSynchronous
params
QueryParams

The parsed query params — mutate directly.

queryPrefixes
QueryPrefixes

The active query prefixes.

route
RouteLocation

The current route.

orchestr:navigate-query:build
Rewrite the assembled query object before Orchestr serializes it into a navigation URL.

When it fires: At the end of buildQueryUrl(), before returning the URL.

Runs onClientRegister inNuxt pluginDispatchSynchronous
params
QueryParams

The params being written.

query
QueryObject

The assembled query object — mutate directly.

path
string

The target path.

queryString
string

The serialized query string.

Client Environment

Client environment

orchestr:client-env:modify
Shape the wire client environment before every request is sent. This is the browser's payload, not what handlers receive: the server validates every field against the project's own configuration and resolves it into a ClientEnv. Nothing you set here is trusted.Frontend Core already sets marketId, languageId, and the content-preview fields. Put your own data under custom, and validate it server-side — a shopper can set it to anything.

When it fires: Synchronously, every time orchestr builds the wire clientEnv before sending a query or action request.

Runs onClientRegister inNuxt pluginDispatchSynchronous
clientEnv
WireClientEnv

{ isPreview, previewToken?, marketId?, languageId?, custom? }, imported from @laioutr-core/core-types/orchestr — mutate directly, do not replace.

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().

  1. beforeorchestr:action:handler:before

    Before the handler runs.

    { token, input, clientEnv }
  2. successorchestr:action:handler:success

    After the handler resolves.

    { token, output }
  3. errororchestr:action:handler:error

    After the handler throws.

    { token, error }
  4. finallyorchestr:action:handler:finally

    Always

    { token, output?, error?, input }
server/plugins/action-logging.ts
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);
  });
});
Copyright © 2026 Laioutr GmbH