@onderwijsin/nuxt-directus-client
v0.12.0
Published
Server-safe Directus REST integration for Nuxt applications.
Readme
@onderwijsin/nuxt-directus-client
Typed, server-safe Directus REST access for Nuxt 4. Use it for browser and SSR requests, preview lookups, generated schema types, normalized errors, and optional cookie-backed authentication.
Install
pnpm add @onderwijsin/nuxt-directus-client @onderwijsin/nuxt-directus-config// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@onderwijsin/nuxt-directus-config", "@onderwijsin/nuxt-directus-client"]
});// directus.config.ts
import { defineDirectusConfig } from "@onderwijsin/nuxt-directus-config/config";
export default defineDirectusConfig({
instance: {
baseUrl: process.env.DIRECTUS_URL,
proxyToken: process.env.DIRECTUS_PROXY_TOKEN
},
client: {
commands: ["readItem", "readItems"]
}
});@onderwijsin/nuxt-directus-config is optional. Without it, configure the same shape directly:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@onderwijsin/nuxt-directus-client"],
directusClient: {
instance: {
baseUrl: process.env.DIRECTUS_URL,
proxyToken: process.env.DIRECTUS_PROXY_TOKEN
},
client: {
commands: ["readItem", "readItems"]
}
}
});Direct module options and directus.config.ts use the same instance and client shape. When both
are present, direct module options take precedence. instance.baseUrl and instance.proxyToken are
server-only. The proxy token is server-held but delegated through the public application proxy, so
its permissions must be safe for public application callers to exercise; secrecy does not make those
permissions private. Never put Directus credentials in runtimeConfig.public or browser code.
instance.baseUrl is optional. The module skips setup during nuxt prepare and CI when it is not
configured; any request made without it fails with a clear runtime error. Set
directusClient.enabled: false when an application does not configure Directus.
Quick start
The default auto-imports are readItem and readItems from @directus/sdk together with the
module's composables:
const articles = await useDirectus(readItems("articles", { limit: 10 }));Browser requests use the same-origin proxy. SSR requests use Directus directly. Credentials are selected on the server, so browser callers cannot override the configured credential.
For Nitro handlers, use the server composable:
export default defineEventHandler((event) =>
useDirectusServer(readItems("articles", { limit: 10 }), event)
);When importing the helper from reusable server-side module code, use the published server runtime entrypoint:
import {
useDirectusServer,
useDirectusServerItemByPath
} from "@onderwijsin/nuxt-directus-client/runtime/server";Composables
All composables below are auto-imported. Import commands that are not configured in
directusClient.client.commands directly from @directus/sdk.
useDirectus
useDirectus<Output>(command: RestCommand<Output, Schema>): Promise<Output>Runs a Directus REST command in Vue code. In the browser, it uses the configured same-origin proxy; during SSR, it talks to Directus directly. The module selects the server-side credential, so callers cannot supply one through request headers.
useDirectusServer
useDirectusServer<Output>(command: RestCommand<Output, Schema>, event?: H3Event): Promise<Output>Runs a command from Nitro. Pass the current event to apply its preview context and, when authentication is enabled, its session.
useDirectusServerAuth
useDirectusServerAuth(event: H3Event): Promise<DirectusSessionSnapshot | null>Resolves the current token-free Directus session snapshot using the request-scoped, refresh-aware
authentication boundary. It returns null when the request is unauthenticated, the sealed session
is invalid, or authentication is disabled. Transient refresh failures propagate to the caller; this
helper does not silently fall back to stale local state. Access and refresh tokens are never
returned.
export default defineEventHandler(async (event) => {
const session = await useDirectusServerAuth(event);
if (!session) {
throw createError({ statusCode: 401 });
}
return { userId: session.userId };
});useDirectusItemByPath
useDirectusItemByPath(collection, query): Promise<Item | null>Returns the first item matching a Directus query, or null. It is intended for route lookups such
as a page by slug and automatically applies the current route's preview context.
useDirectusServerItemByPath
useDirectusServerItemByPath(event, collection, query): Promise<Item | null>The Nitro equivalent of useDirectusItemByPath. Use it from server handlers when the lookup must
read preview values from the request event.
useDirectusError
useDirectusError(error: unknown): DirectusErrorResultNormalizes Directus, SDK, ofetch, H3, and malformed errors. The result has isDirectusError,
isNitroError, a safe errors list, an optional statusCode, and flags for OTP, invalid
credentials, forbidden, expired or invalid tokens, validation, rate-limit, service-unavailable, and
route-not-found errors. Local authentication validation failures are marked with isNitroError and
expose isInvalidAuthInput, isInvalidEmailInput, isInvalidPasswordInput, isInvalidOtpInput,
isInvalidPasswordResetTokenInput, and isInvalidMagicLinkTokenInput. Their errors entries use
Nitro codes such as INVALID_PASSWORD_INPUT and preserve safe validation details including the
field, message, and maximum length. Unknown errors return both discriminator flags as false with
an empty errors list.
useDirectusAuth
useDirectusAuth is available only when client.auth.enabled is true. Its full state and method
reference is in Authentication.
Configuration
All options are configured under directusClient:
| Option | Default | Contract |
| ------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| enabled | true | Enables the module. |
| instance.baseUrl | — | Optional Directus URL. Required before requests can run. |
| instance.proxyToken | — | Server-held credential delegated through the proxy; its permissions must be safe for public callers. |
| client.proxy.path | /_directus/proxy | Absolute local same-origin browser proxy path. Root paths, auth-route collisions, and overlaps with client.assets.path are rejected. |
| client.assets.enabled | true | Registers the dedicated Directus /assets proxy when enabled. |
| client.assets.url | — | Optional absolute upstream asset base URL; defaults to instance.baseUrl with /assets. |
| client.assets.path | /_directus/assets | Absolute local asset-proxy path; uses the same safe local-path validation and cannot overlap the REST proxy or reserved auth routes. |
| client.assets.publicOnly | false | Uses anonymous Directus asset requests only; session authentication is never attempted when enabled. |
| client.assets.cache.enabled | false | Enables server-side caching for explicitly public anonymous asset responses. |
| client.assets.cache.storage | — | Nitro storage mount name, required when enabled; it must support raw binary values. |
| client.assets.cache.maxAge | — | Positive fresh cache lifetime in seconds, required when enabled. |
| client.assets.cache.maxBodySize | 10485760 | Maximum response size in bytes that may be buffered for caching. |
| client.assets.cache.swr | false | Enables stale-while-revalidate behavior. |
| client.assets.cache.staleMaxAge | — | Optional non-negative stale lifetime in seconds. |
| client.assets.cache.prune.enabled | false | Opts into pruning expired entries when storage does not enforce physical TTLs. |
| client.assets.cache.prune.onRequest | true | Enables throttled background pruning after cached asset requests. |
| client.assets.cache.prune.interval | 3600 | Minimum interval between request-triggered prune attempts, in seconds. |
| client.commands | [readItem, readItems] | SDK commands to auto-import. Unsupported names are rejected. |
| client.preview.enabled | false | Enables preview query parsing and request-scoped preview credentials; set to true to opt in. |
| client.preview.versioning | true | Enables versioned preview lookup. |
| client.preview.queryKeys | preview, token, version, id | Query parameter names used for preview context. |
| client.auth.enabled | false | Enables cookie authentication, authentication routes, and useDirectusAuth. |
| client.auth.turnstile.enabled | false | Registers Turnstile and protects login plus password-reset-email requests. |
| client.auth.magicLinks.enabled | false | Registers optional magic-link request and redemption routes; requires auth to be enabled. |
| client.auth.magicLinks.redirectUrl | — | Fixed absolute callback URL sent upstream; required when enabled and server-only. |
| client.auth.cookie.name | directus_session | Session cookie name. |
| client.auth.cookie.secure | true | Sends the cookie only over HTTPS. Use false only for local HTTP development. |
| client.auth.cookie.sameSite | lax | Cookie SameSite policy. |
| client.auth.cookie.path | / | Cookie path. |
| client.auth.cookie.maxAge | 2592000 | Cookie lifetime in seconds. |
| client.auth.cookie.domain | — | Optional cookie domain. |
| client.auth.refreshSafetyWindow | 30000 | Refreshes a session this many milliseconds before expiry. |
| client.auth.sessionSecret | — | Server-only H3 sealing secret; required when auth is enabled and must contain at least 32 characters. |
| client.auth.previousSessionSecrets | [] | Server-only previous sealing secrets tried during key rotation, in order. |
| client.auth.maskSecretsInPlayground | true | Masks tokens in the local sealed-session playground inspection page. |
| client.auth.passwordResetUrl | — | Required for password-request support; sent to Directus as reset_url. |
| client.auth.user.enabled | false | Enables the opt-in current-user fetch; requires authentication. |
| client.auth.user.fields | — | Required non-empty recursive Directus QueryFields selection when enabled. |
| client.auth.user.mapper | — | Server-only synchronous mapper from executable shared config; not accepted in raw Nuxt options. |
| client.typegen.enabled | true | Enables generated #directus declarations. |
| client.typegen.introspectionToken | — | Server-only Directus schema introspection token. |
| client.typegen.cache.maxAge | 3600000 | Development type-generation cache lifetime in milliseconds. |
| client.typegen.augmentations | all true | Optional generated-output transforms. |
| client.typegen.rules | {} | Generated field type overrides keyed by collection and field. |
| client.typegen.transform | — | Final build-time source transform. |
The module validates options during Nuxt configuration. Production and CI type generation require
both instance.baseUrl and client.typegen.introspectionToken when it is enabled.
The asset proxy normalizes upstream Vary headers to Accept, matching the request representation
that the proxy exposes and the optional asset cache keys. It does not vary on Origin, request
Cache-Control, or Accept-Encoding.
Asset delivery uses a streaming proxy when caching is disabled. When caching is enabled, only
anonymous public responses participate in the application-scoped asset cache. If an asset requires
the current session, its response is marked Cache-Control: private, no-store for downstream
clients.
Pruning is opt-in for storage backends that do not reliably expire entries. Request-triggered
pruning runs in the background and is throttled by client.assets.cache.prune.interval; it never
blocks asset delivery. The package also exports an optional Nitro task for consumers to register
manually:
client: {
assets: {
cache: {
prune: { enabled: false, onRequest: true, interval: 3600 }
}
}
}// server/tasks/directus-assets/prune.ts
export { default } from "@onderwijsin/nuxt-directus-client/runtime/prune-task";Enable Nitro's experimental tasks and optionally schedule directus-assets:prune in the consumer
application. The module does not enable task infrastructure or add a schedule automatically:
export default defineNuxtConfig({
nitro: {
experimental: { tasks: true },
scheduledTasks: { "0 * * * *": ["directus-assets:prune"] }
}
});Version previews
Directus Content Versions are independent, unpublished changes to a main item. A version has a
key—the value Directus uses in the REST version query parameter—and the main item remains the
canonical item. When the preview URL contains preview=true, an item id, and a version key,
useDirectusItemByPath and useDirectusServerItemByPath read that item with the selected version.
Without a valid version context, they retain their normal first-matching-item lookup behavior.
Configure Directus before relying on version previews:
- In Settings → Data Model, enable Content Versioning for each previewed collection and create versions in the item editor.
- Set the collection's Preview URL to your application route. Insert the item ID and
Version dynamic values so it supplies the module's default query names, for example
https://app.example.test/pages/{{slug}}?preview=true&id={{id}}&version={{version}}. - Previewing unpublished content also needs a credential that can read it. We recommend
Tokenized Preview Endpoint for Directus:
install it in Directus, configure its preview base URL, and prefix the collection Preview URL
with its
/preview/endpoint. It appends a short-livedtokento the application URL. Keep itsTOKENIZED_PREVIEW_TOKEN_KEYastoken, or setclient.preview.queryKeys.tokento match.
For example, an extension-backed URL can be configured as:
// Directus collection Preview URL (use Directus' dynamic-value picker for the placeholders)
/preview/https://app.example.test/pages/{{slug}}?preview=true&id={{id}}&version={{version}}Preview handling is disabled by default. Set client.preview.enabled to true to opt in. The
default preview query keys are preview, token, version, and id; they can be renamed with
client.preview.queryKeys. Tokens stay request-scoped and are never exposed through public runtime
configuration. Set client.preview.enabled to false to ignore all preview parameters, or
client.preview.versioning to false to ignore only the version. This section covers credentialed
lookup, not the embedded editor; see Live preview and framing for that
setup.
Authentication
Enable authentication with directusClient.client.auth.enabled: true:
const auth = useDirectusAuth();
await auth.login({
email: "[email protected]",
password: "password",
otp: "123456"
});
if (auth.isAuthenticated.value) {
console.log(auth.userId.value);
}The session snapshot is persisted with the access and rotating refresh token in a bounded sealed
httpOnly cookie. The snapshot contains only stable authentication facts (userId and
requiresTfaSetup); mutable profile data is never stored in the cookie. SSR refreshes an expiring
access token when possible before projecting the snapshot into Nuxt state, so hydration does not
require a session fetch. If refresh is temporarily unavailable, SSR falls back to the trusted local
snapshot; terminal authentication failures still clear the session. Access and refresh tokens never
enter client state or application code. H3 authenticated encryption protects the cookie's
confidentiality and integrity; Directus remains the authorization boundary.
Authentication mutations use Nuxt's request-aware fetch against the same-origin /_directus/auth/
endpoints. The composable remains SSR-safe: reading isAuthenticated, userId, and the session
state works during SSR. Automatic SSR session refresh happens directly through the Nitro request
boundary, not through an internal HTTP refresh call. Authenticated upstream requests remain strict:
they do not send an expired or unusable credential when refresh is temporarily unavailable.
The authentication boundaries have distinct responsibilities: getDirectusSessionSnapshot(event)
reads trusted local session state without refreshing and is an internal server primitive;
useDirectusServerAuth(event) represents current server authentication state and is refresh-aware;
and directusAuth.resolve() resolves request-scoped usable credentials and authentication state.
SSR normally uses refresh-aware resolution, but falls back to getDirectusSessionSnapshot(event)
only for an explicitly classified transient refresh failure.
Mutations that do not depend on writing a new browser cookie can work naturally through the internal
route. Login, refresh, logout, and magic-link redemption may require response-cookie propagation
when invoked during SSR because Set-Cookie from a nested internal request is not automatically
equivalent to writing on the outer SSR response. Initial SSR POST mutations may also be subject to
the same-origin and CSRF requirements described below. Do not rely on SSR mutation calls for
establishing or clearing a browser session unless the outer response explicitly propagates the
cookie.
When authentication is enabled, configure client.auth.sessionSecret from a cryptographically
random, server-only value of at least 32 characters. Existing unsigned cookies are rejected and
cleared. To rotate a secret without signing everyone out, put the old value in
previousSessionSecrets; new or read sessions are sealed with the active secret and old values are
removed after the migration overlap period. H3's derived session header is disabled for this module
(sessionHeader: false), so the Directus session is accepted only from the configured cookie.
Generate a session secret with:
openssl rand -base64 32During local development, the module supplies a fixed convenience secret when authentication is
enabled without an explicit value. nuxt prepare and CI use a fresh cryptographically random
ephemeral secret so generated or tested artifacts never inherit the development value. Production
does not provide a fallback: configure an explicit deployment secret to keep sessions stable across
builds and deployments.
useDirectusAuth API
The composable exposes a token-free, reactive session snapshot:
| State | Type | Contract |
| ------------------------ | ---------------------------------------------------- | --------------------------------------------------------------------- |
| auth._session | DeepReadonly<Ref<DirectusSessionSnapshot \| null>> | Read-only snapshot containing identity fields and requiresTfaSetup. |
| auth.isAuthenticated | DeepReadonly<ComputedRef<boolean>> | true when a snapshot exists. |
| auth.userId | DeepReadonly<ComputedRef<string \| undefined>> | Current user ID. |
| auth.magicLinksEnabled | boolean | Whether the magic-link facade is enabled. |
| auth.requiresTfaSetup | DeepReadonly<ComputedRef<boolean>> | Server-derived informational TFA setup requirement. |
The snapshot contains userId and requiresTfaSetup, which reflects Directus' enforce_tfa claim.
It deliberately contains no profile fields, access or refresh token, role, policy, or permission
helpers. requiresTfaSetup is informational; the consuming application owns any TFA setup UX or
navigation.
| Method | Signature | Behavior |
| ------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| login | login({ email, password, otp? }, meta?): Promise<void> | Authenticates, fetches only the stable user ID for the session, writes the session cookie, updates state, and emits directus:auth:login. |
| refresh | refresh(): Promise<void> | Refreshes and rotates the token pair without refetching profile data, updates state, and emits directus:auth:refresh. Terminal session/auth rejection clears state and emits directus:auth:invalidated; transient refresh availability failures preserve state and rethrow. |
| logout | logout(): Promise<void> | Attempts upstream logout, always clears local state and cookie, and emits directus:auth:logout. An upstream failure is rethrown after cleanup. |
| passwordRequest | passwordRequest(email, meta?): Promise<void> | Requests a password-reset email using client.auth.passwordResetUrl. |
| passwordReset | passwordReset(token, password): Promise<void> | Completes a Directus password reset. |
| requestMagicLink | requestMagicLink(email, meta?): Promise<void> | Requests a passwordless login link when magic links are enabled. |
| redeemMagicLink | redeemMagicLink(token, otp?): Promise<void> | Redeems a token, establishes the normal session, and emits directus:auth:login. |
meta may be { turnstileToken?: string } when Turnstile protection is enabled.
Current user
Current-user data is opt-in and separate from authentication:
export default defineDirectusConfig({
client: {
auth: {
enabled: true,
user: {
enabled: true,
fields: ["id", "email", "first_name", "last_name", { role: ["id", "name"] }]
}
}
}
});fields is required when enabled, must be non-empty, and supports nested Directus QueryFields. The
default is { enabled: false }; the module never expands it to * or adds id implicitly.
Use useDirectusUser() for mutable profile/application data:
const { user, status, error, refresh } = useDirectusUser();
await useDirectus(updateMe(payload));
await refresh();The composable shares the stable directus:user async-data key and uses GET /_directus/auth/user
in both browser and SSR. The route has private, no-store semantics and preserves rotated session
cookies during SSR. Unauthenticated state is user === null without a fabricated 401. Login
refreshes existing user state, logout and invalidation clear it, and token refresh does not refetch
it. The user ref is generated from the configured fields. With automated type generation it uses
the generated DirectusUser, including custom system-collection fields; when type generation is
disabled it falls back to the SDK user type. An executable mapper replaces either selection with its
inferred return type.
An executable directus.config.ts may add a synchronous server-only mapper that returns a plain
object. Its parameter exposes selected SDK user fields as optional values, while custom fields
remain unknown until narrowed. Register @onderwijsin/nuxt-directus-config when using a mapper.
Profile mutations require an explicit refresh() when immediate local freshness is needed.
Magic links
Install the directus-magic-links-bundle extension in Directus, then enable magic links with a
fixed callback URL:
export default defineNuxtConfig({
directusClient: {
client: {
auth: {
enabled: true,
magicLinks: {
enabled: true,
redirectUrl: "https://app.example.test/auth/magic-link"
}
}
}
}
});Request and redeem links through the existing authentication facade:
const auth = useDirectusAuth();
await auth.requestMagicLink("[email protected]");
await auth.redeemMagicLink(token, otp);The configured callback URL is fixed server configuration; the browser cannot override it. Directus
access and refresh tokens remain in the sealed HTTP-only session. auth.requiresTfaSetup exposes
the server-derived enforce_tfa state as informational data. The consuming application owns the
callback page, URL token extraction and cleanup, OTP UI, TFA setup navigation, return-to state, and
post-login navigation. When magic links are disabled, these facade methods perform no network
request.
Directus MFA failures are exposed through useDirectusError(error).isOtpError, allowing the UI to
ask for an OTP and retry auth.login or auth.redeemMagicLink. Local redemption token validation
uses INVALID_MAGIC_LINK_TOKEN_INPUT and isInvalidMagicLinkTokenInput.
When client.auth.enabled is false, Directus session cookies are ignored: they are not read,
refreshed, forwarded upstream, or added to the SSR payload. Proxy, preview, and unauthenticated
access remain available.
Authentication routes are registered under /_directus/auth/: login, refresh, logout,
session, password-request, and password-reset. SSR authentication bootstrap, the session
route, and authenticated Directus requests refresh when the access token enters the configured
safety window. They can also refresh an access token that has already expired; Directus decides
whether the refresh token is still valid. The refresh request is attempted exactly once because
Directus may rotate the refresh token even when a response is lost. The existing safe user snapshot
is reused after rotation; only access-token-derived state such as requiresTfaSetup is
recalculated. If a later local persistence step fails, the old session is cleared because its
refresh token may already be invalid. Refresh coordination uses the directus-auth-refresh Nitro
mount: the memory driver provides process-local single-flight coordination, while the redis
driver provides atomic cross-process coordination using its configured Redis client. Configure Redis
for multiple Node processes, containers, replicas, or Cloudflare isolates; unsupported drivers fail
explicitly. Refresh results are H3-sealed and short-lived, and the configured Redis backend must be
treated as sensitive infrastructure. Completed results are reusable for thirty seconds, terminal
results for five seconds, and transient failures for only one second to suppress an immediate
request burst. This bounded reuse window protects overlapping and near-concurrent refreshes; it is
not permanent consumed-token tracking.
When client.auth.magicLinks.enabled is true, the module additionally registers
POST /_directus/auth/magic-links/request and POST /_directus/auth/magic-links/redeem. These
routes require the Directus magic-links extension. The request route uses the fixed server-side
callback URL and redemption always requests Directus mode: "json", establishing the normal sealed
session.
Rotating the session secret
Rotate client.auth.sessionSecret in two deployments: first set the new secret as active while
keeping the old secret in client.auth.previousSessionSecrets, then remove the old secret only
after all old cookies could have expired. Keep the overlap at least as long as
client.auth.cookie.maxAge plus a short deployment window so rolling instances can read and reseal
existing sessions. Removing a previous secret invalidates cookies still sealed with it. Keep the old
secret available during the overlap on every instance, and treat the shared Nitro storage mount as
sensitive while sealed refresh results from the previous deployment can remain within its short TTL.
The login and password-request routes accept emails up to 1024 characters. Login passwords and password-reset passwords may be up to 512 characters, login OTPs up to 6 characters, and password-reset tokens up to 1024 characters. Oversized values are rejected before they are forwarded to Directus.
Authentication mutations require an Origin or Referer matching the application origin. Missing
or cross-origin metadata is rejected with 403, including when the session cookie uses
sameSite: "none".
The sealed-session inspection endpoint belongs only to the local playground and returns 404 in
production builds. Keep the playground out of production deployments and leave secret masking
enabled unless a local diagnostic explicitly requires otherwise.
Turnstile protection
Set client.auth.turnstile.enabled: true to register @onderwijsin/nuxt-turnstile and require a
Turnstile token for login, password-reset-email, and magic-link request operations. Configure the
Turnstile site and secret keys through the usual top-level turnstile option. Follow the
Turnstile module guide to configure keys, render the widget, and manage
the token lifecycle. The Directus module exposes the required widget actions through public runtime
config, and the optional second argument to each auth method forwards the token in
x-turnstile-token:
const config = useRuntimeConfig();
const auth = useDirectusAuth();
const token = await getTokenWithRetry();
await auth.login({ email: "[email protected]", password: "password" }, { turnstileToken: token });
const passwordRequestAction = config.public.directusClient.auth.turnstile.actions.passwordRequest;Use config.public.directusClient.auth.turnstile.actions.login for the login widget and
passwordRequest for the password-reset-request widget and magicLinkRequest for the magic-link
request widget. Cloudflare's test credentials return a verified test-key response without an action,
which the module recognizes only for those credentials. Tokens are required only when the option is
enabled; reset each widget after its submission because Turnstile tokens are single-use.
Generated types
When type generation is enabled, the module uses
directus-sdk-typegen to inspect your
Directus schema and expose collection interfaces plus Schema from the virtual #directus module:
import type { Article, Schema } from "#directus";Configure it under directusClient.client.typegen:
| Option | Use it when |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| enabled | Set to false to skip generation. An existing declaration is reused when available; otherwise Schema is empty. |
| introspectionToken | Supply a server-only token with enough Directus permissions to inspect the schema. It is required for enabled generation outside local development. |
| cache.maxAge | Adjust the development-only cache lifetime (milliseconds). CI and production always regenerate. |
| augmentations | Individually disable a generated-output transform when its default normalization does not fit your schema. All default to true. |
| rules | Replace a generated field type deterministically, keyed by collection then field: { articles: { body: "RichText" } }. Values must be a single TypeScript type expression. |
| transform | Apply a final build-time function to the generated source. It receives the source and metadata including the Directus URL, generator version, collection names, and rules, and must return source text. |
| Augmentation | Effect |
| ---------------------------------- | ------------------------------------------------------------------------------ |
| removeEnums | Removes generated export enum declarations. |
| replaceAnyWithUnknown | Rewrites generated Record<…, any> values to Record<…, unknown>. |
| replaceJsonWithJSON | Rewrites quoted "json" field types to JSON. |
| makeNonNullableOptionalsRequired | Makes simple optional fields required when their type does not include null. |
| mergeJsDocs | Merges adjacent generated JSDoc tag blocks and removes duplicate tags. |
They run in the shown order before rules, followed by transform. Missing or incomplete
credentials reuse the previous declaration—or produce an empty Schema—only in local development;
CI and production fail clearly instead.
Proxy endpoint
The browser endpoint at proxy.path (default /_directus/proxy) forwards REST requests to the
configured Directus instance. This lets browser code use useDirectus without learning the Directus
URL or receiving a proxy, preview, or session token. The server chooses credentials in this order:
current session when authentication is enabled, preview token, proxy token, then no credential.
Preview/version selection is independent from credential selection, so a preview URL does not
replace an authenticated session credential.
The proxy preserves the request method, body, query string, response status, and safe response
headers. It forwards only REST headers needed for representation, caching, conditional requests,
range, and preference semantics. Caller-supplied credential, cookie, host, origin, Referer,
connection, hop-by-hop, forwarding, client-IP, and platform identity headers are not forwarded; the
preview token is stripped from the upstream query; and upstream Set-Cookie and Access-Control-*
headers are never returned to the browser. Upstream non-2xx status codes and bodies are preserved,
while network failures remain proxy errors. Credentialed mutations (POST, PUT, PATCH, and
DELETE) require a same-origin Origin or Referer header.
It is not a general-purpose proxy, a CORS bypass, a session-token API, or an authorization layer. It only targets the configured Directus URL, and Directus permissions remain the final access control.
Live preview and framing
Configure Directus Live Preview to open the application URL with the module's preview query
parameters. The application must allow Directus in its frame-src policy, and the application must
be allowed by its own frame-ancestors policy. A version placeholder should be included in the
configured preview URL when versioned content is required. The module does not refresh pages
automatically; the application decides how to react to iframe updates.
Troubleshooting
- A browser request failing with a Directus permission error is expected when the selected session, proxy token, or unauthenticated role lacks access. The proxy is not an authorization layer.
- A missing generated type in production usually means
DIRECTUS_URLorDIRECTUS_INTROSPECTION_TOKENwas not available duringnuxt prepare/build. - A local auth cookie normally needs
client.auth.cookie.secure: falsewhen the playground is served over plain HTTP. Keep the secure default in deployed environments. - A preview lookup returns
nullwhen the application path filter matches no item; preview mode does not change lookup semantics or turn an item path into a Directus primary key.
Security and compatibility
- Directus URLs, credentials, and session tokens are server-only.
- Browser requests cross the same-origin proxy, which strips caller-supplied credential and origin headers.
- State-changing proxy requests that use a server credential require a matching
OriginorRefererheader. Cross-origin or headerless mutations are rejected, including whensameSite: "none"is configured. - Authentication
POSTroutes apply the same origin validation before reading input or changing a session. - Directus permissions remain the final authorization boundary.
- Supported environments are Nuxt 4 and Node.js 24 or newer. Node.js 22 may work but is untested and unsupported.
