Directus Integration Guide
This guide will walk you through integrating OpenAPI Qraft with Directus, a headless CMS platform. You'll learn how to generate a type-safe API client that leverages Directus's powerful query parameter system, including filters, sorting, field selection, and metadata options, all with full TypeScript type safety.
Introductionโ
Directus provides a comprehensive REST API with a sophisticated query parameter system that includes:
- Filters: Complex filtering with operators like
_eq,_in,_null, and many more (see Directus Query Parameters - Filter) - Field selection: Choose which fields to return, including nested relational fields with dot notation
- Sorting: Sort results by one or more fields, ascending or descending
- Pagination: Control results with
limit,offset, andpageparameters - Metadata: Request additional metadata like
filter_countortotal_countfor efficient counting - Search: Full-text search across string and text fields
- Aggregation: Perform calculations with aggregate functions
- And many more advanced features
The key feature of this integration is that all of these Directus query parameters are fully typed when using the generated API client. Instead of working with generic query strings or untyped objects, you get:
- Full TypeScript autocomplete for Directus query syntax
- Type-safe filter operators and field names
- Compile-time validation of query structure
- Seamless integration with Directus SDK's
Querytype
Prerequisitesโ
Before starting, ensure you have:
- A working Directus instance (local or remote)
- OpenAPI specification file from your Directus instance
Installing Required Packagesโ
Install the necessary packages for code generation and runtime:
- npm
- yarn
- pnpm
npm install -D @openapi-qraft/cli
npm install @openapi-qraft/react @openapi-qraft/tanstack-query-react-types @directus/sdk
npm install @tanstack/react-query
yarn add --dev @openapi-qraft/cli
yarn add @openapi-qraft/react @openapi-qraft/tanstack-query-react-types @directus/sdk
yarn add @tanstack/react-query
pnpm add -D @openapi-qraft/cli
pnpm add @openapi-qraft/react @openapi-qraft/tanstack-query-react-types @directus/sdk
pnpm add @tanstack/react-query
For more details on installation, see the Installation guide.
Creating the Configurationโ
The recommended approach is to use a Redocly configuration file (apis.yaml or redocly.yaml) to manage
your API client generation. This allows you to centralize settings and easily manage multiple APIs.
Configuration File Structureโ
ยง
Create an apis.yaml file in your project root (or wherever you prefer to store your configuration):
# OpenAPI Qraft configuration
# Global plugins configuration
x-openapi-qraft:
plugin:
tanstack-query-react: true
openapi-typescript: true
apis:
# Directus API configuration
directus:
root: src/api/directus/openapi.json
x-openapi-qraft:
output-dir: src/api/directus
clean: true
# Filter services to exclude utils endpoints
filter-services:
- '/**'
- '!/utils/**'
# Configure custom ParametersWrapper types for specific operations
# You can apply these wrappers to any Directus endpoints (items, users, files, etc.)
# Patterns don't need to specify HTTP methods - they apply to all methods
# If you need to target a specific method, you can use: 'get /items/*' or 'post /items/*'
operation-parameters-type-wrapper:
# For operations that return arrays (covers first-level paths like /items, /users, etc.)
# The '/*,!/fields' pattern means: all first-level paths except /fields
'/*,!/fields':
type: 'ItemsQueryParametersWrapper'
import: './types'
# For operations that return a single item (e.g., /items/collection/{id})
# The '/*/{id},!/fields/{id}' pattern means: all second-level paths with {id} except /fields/{id}
'/*/{id},!/fields/{id}':
type: 'SingleItemsQueryParametersWrapper'
import: './types'
# You can also specify specific endpoints for nested paths if needed
'/items/*':
type: 'ItemsQueryParametersWrapper'
import: './types'
'/items/*/{id}':
type: 'SingleItemsQueryParametersWrapper'
import: './types'
Configuration Options Explainedโ
root: Path to your Directus OpenAPI specification fileoutput-dir: Directory where generated files will be placedclean: Automatically clean the output directory before generationfilter-services: Glob patterns to include/exclude specific endpointsoperation-parameters-type-wrapper: Configure custom parameter types for specific operations. For Directus integration, this is particularly important for operations that use query parameters (like/items/*,/users, etc.), where Directus's powerful query parameters (filters, sorting, field selection, metadata, etc.) need to be properly typed using Directus SDK'sQueryandQueryItemtypes instead of generic OpenAPI parameter types. Patterns don't need to specify HTTP methods - they apply to all methods. You can use negative patterns (e.g.,'/*,!/fields') to exclude specific endpoints from the wrapper.
For more information about Redocly configuration options, see the Redocly config support documentation.
Creating a Custom ParametersWrapper Typeโ
Directus uses a sophisticated query system that requires custom parameter handling. To properly type Directus queries, you'll need to create a custom ParametersWrapper type.
Type Definitionโ
Create a file src/api/directus/types.ts with the following content:
import type { Query, QueryItem } from '@directus/sdk';
import { components } from './schema';
// For operations that return an array of items (e.g., /items/collection, /users)
export type ItemsQueryParametersWrapper<
TSchema,
TOperation extends { parameters: { query?: any } },
TData,
TError,
> = TOperation['parameters'] extends { query?: { fields?: any } }
? TData extends { data?: any[] }
? Merge<
TOperation['parameters'],
{
query?: QueryItems<TData>;
}
>
: TOperation['parameters']
: TOperation['parameters'];
// For operations that return a single item (e.g., /items/collection/{id})
export type SingleItemsQueryParametersWrapper<
TSchema,
TOperation extends { parameters: { query?: Record<string, any> } },
TData,
TError,
> = TData extends { data?: any }
? NonNullable<TOperation['parameters']['query']> extends {
fields?: string[];
meta?: string;
version?: string;
}
? NonNullable<NonNullable<TOperation['parameters']['query']>['version']> extends string
? Merge<
TOperation['parameters'],
{
query?: Pick<QuerySingleItems<TData>, 'fields' | 'meta' | 'version'>;
}
>
: Merge<
TOperation['parameters'],
{
query?: Pick<QuerySingleItems<TData>, 'fields' | 'meta'>;
}
>
: TOperation['parameters']
: TOperation['parameters'];
type Merge<A extends Record<string, any>, B extends Record<string, any>> = Omit<A, keyof B> & B;
type QuerySingleItems<Item extends { data?: any }> = QueryItem<
Schema,
NonNullable<Item['data']>
> & {
meta?: Meta;
};
type QueryItems<Item extends { data?: any }> = Query<Schema, NonNullable<Item['data']>[number]> & {
meta?: Meta;
};
type Meta = 'filter_count' | 'total_count';
type Schema = {
[key in keyof components['schemas']]: components['schemas'][key][];
};
Understanding the Typesโ
Both wrapper types accept four generic parameters:
TSchema: The operation schema containing method and URL information (automatically provided by the code generator)TOperation: The operation type from OpenAPI paths, containing the original parameters definitionTData: The response data type, used to infer the item type for Directus queriesTError: The error type (automatically provided by the code generator)
When to use each type:
ItemsQueryParametersWrapper: Use for operations that return an array of items (e.g.,/items/collection,/users). The type intelligently checks if the operation has query parameters withfields, and if the response is an array, it merges the original parameters with DirectusQuerytype for proper typing.SingleItemsQueryParametersWrapper: Use for operations that return a single item (e.g.,/items/collection/{id}). The type checks forfields,meta, andversionparameters and conditionally applies DirectusQueryItemtype with appropriate field selection.
How It Worksโ
Omit<TOperation['parameters'], 'query'>: Removes the defaultqueryparameter from the operation's parameters- Custom
queryproperty: Replaces it with Directus'sQuerytype from@directus/sdk, which provides:- Type-safe field selection
- Filtering capabilities
- Sorting options
- Pagination controls
metaparameter: Adds support for Directus's metadata options (filter_count,total_count) for efficient counting operations
The generic parameters TSchema, TData, and TError may appear unused in the type body, but they are required
for proper type inference and are passed by the code generator. If your linter flags them as unused, you can disable the rule for this file using:
/* eslint-disable @typescript-eslint/no-unused-vars */
Generating the API Clientโ
Once your configuration is set up, you can generate the API client using the Qraft CLI.
Using Redocly Configurationโ
The simplest approach is to use the Redocly configuration file:
- npm
- yarn
- pnpm
npx openapi-qraft directus --redocly apis.yaml
yarn exec openapi-qraft directus --redocly apis.yaml
pnpm exec openapi-qraft directus --redocly apis.yaml
This command will:
- Read the configuration from
apis.yaml - Generate TypeScript types from the OpenAPI specification
- Generate React Query hooks for all configured endpoints
- Apply the custom
ItemsQueryParametersWrapperandSingleItemsQueryParametersWrappertypes where specified
Adding a Script to package.jsonโ
For convenience, add a script to your package.json:
{
"scripts": {
"generate-api-client:directus": "openapi-qraft directus --redocly apis.yaml"
}
}
Then run:
- npm
- yarn
- pnpm
npm run generate-api-client:directus
yarn generate-api-client:directus
pnpm run generate-api-client:directus
Alternative: Using CLI Options Directlyโ
If you prefer not to use a configuration file, you can specify options directly:
- npm
- yarn
- pnpm
npx openapi-qraft \
--plugin tanstack-query-react \
--plugin openapi-typescript \
--output-dir src/api/directus \
--clean \
--filter-services '/**' '!/utils/**' \
--operation-parameters-type-wrapper '/*,!/fields' 'type:ItemsQueryParametersWrapper' 'import:./types' \
--operation-parameters-type-wrapper '/*/{id},!/fields/{id}' 'type:SingleItemsQueryParametersWrapper' 'import:./types' \
./src/api/directus/openapi.json
yarn exec openapi-qraft \
--plugin tanstack-query-react \
--plugin openapi-typescript \
--output-dir src/api/directus \
--clean \
--filter-services '/**' '!/utils/**' \
--operation-parameters-type-wrapper '/*,!/fields' 'type:ItemsQueryParametersWrapper' 'import:./types' \
--operation-parameters-type-wrapper '/*/{id},!/fields/{id}' 'type:SingleItemsQueryParametersWrapper' 'import:./types' \
./src/api/directus/openapi.json
pnpm exec openapi-qraft \
--plugin tanstack-query-react \
--plugin openapi-typescript \
--output-dir src/api/directus \
--clean \
--filter-services '/**' '!/utils/**' \
--operation-parameters-type-wrapper '/*,!/fields' 'type:ItemsQueryParametersWrapper' 'import:./types' \
--operation-parameters-type-wrapper '/*/{id},!/fields/{id}' 'type:SingleItemsQueryParametersWrapper' 'import:./types' \
./src/api/directus/openapi.json
For a complete list of CLI options, see the CLI documentation.
Creating a Custom requestFn for Directusโ
While OpenAPI Qraft provides a default requestFn that works with most APIs,
Directus has built-in functionality in its SDK for correctly processing complex query parameters.
The Directus SDK handles sophisticated query parameters (filters, field selection, etc.)
reliably through its REST transport, ensuring proper encoding and processing of all Directus-specific query syntax.
To properly integrate Directus with OpenAPI Qraft, you'll need to create a custom requestFn that:
- Uses Directus SDK's REST client to handle query parameters correctly
- Intercepts the raw HTTP response to allow Qraft to process it
- Maintains compatibility with Qraft's response handling and error processing
Creating the requestFn Fileโ
Create a file src/api/directus/requestFn.ts with the following implementation:
import type { HttpMethod } from '@directus/sdk';
import type { OperationSchema, RequestFnInfo, RequestFnResponse } from '@openapi-qraft/react';
import { rest } from '@directus/sdk';
import { bodySerializer, mergeHeaders, urlSerializer } from '@openapi-qraft/react';
import { processResponse, resolveResponse } from '@openapi-qraft/react/unstable__responseUtils';
export function directusRequestFn<TData, TError>(
requestSchema: OperationSchema,
requestInfo: RequestFnInfo
): Promise<RequestFnResponse<TData, TError>> {
// Build the URL path (e.g., /items/artworks/{id}) with path parameters substituted
// baseUrl is empty here because it will be used when creating the Directus client below
// query parameters are undefined because we only need the path here; query params will be handled by Directus SDK separately
const path = urlSerializer(requestSchema, {
...requestInfo,
baseUrl: '', // Will be used when creating the Directus client
parameters: { ...requestInfo.parameters, query: undefined }, // Only path parameters, query handled separately
});
let response: Response;
/**
* Custom fetch wrapper that intercepts the actual response.
*
* The Directus REST client requires fetch to return a Response object, but if we return
* the actual response, Directus SDK will attempt to parse it using its internal mechanism.
* To avoid this and let Qraft handle response processing instead, we:
* 1. Store the real response in the local `response` variable
* 2. Return an empty Response object to satisfy Directus SDK requirements
*
* The actual response is then used later via `processResponse(response)` and
* `resolveResponse` for proper error handling using Qraft's internal utilities.
*/
const fetch = (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
return globalThis.fetch(input, {
...init,
// Include cookies in requests for Directus cookie-based authentication
// This ensures that authentication cookies are sent with each request
credentials: 'include'
}).then(fetchResponse => {
response = fetchResponse;
return new Response();
});
};
// Initialize Directus REST client with custom fetch wrapper
const client = rest()({
url: new URL(requestInfo.baseUrl ?? ''),
with(createExtension) {
return {
...this,
...createExtension(this),
};
},
globals: {
fetch,
WebSocket: globalThis.WebSocket,
URL: globalThis.URL,
logger: globalThis.console,
},
});
// Serialize request body if present
const bodyInfo = requestInfo.body ? bodySerializer(requestSchema, requestInfo.body) : undefined;
// Merge headers from parameters, body, and request info
const headers = mergeHeaders(
requestInfo.parameters?.header,
bodyInfo?.headers,
requestInfo.headers
);
// Execute the request through Directus SDK
return (
client
.request(() => ({
path,
method: requestSchema.method.toUpperCase() as HttpMethod,
// Pass query parameters to Directus SDK for proper processing
params: requestInfo.parameters?.query,
headers: Object.fromEntries(headers),
body: bodyInfo?.body as FormData,
}))
// The result from client.request() is ignored because Directus SDK has already
// processed the JSON internally. We use the intercepted raw response instead
// and process it ourselves using Qraft's processResponse utility.
.then(() => processResponse<TData, TError>(response))
.catch(resolveResponse as typeof resolveResponse<TData, TError>)
);
}
Why This Is Necessaryโ
Directus uses a sophisticated query parameter system that includes:
- Complex filter syntax (e.g.,
filter[field][_eq]=value) - Nested relational queries
The Directus SDK's REST client knows how to properly encode these parameters,
but the standard requestFn from OpenAPI Qraft would treat them as simple URL query strings,
which could lead to incorrect encoding or missing functionality.
Using the Generated Client in Reactโ
After generation, you can use the generated API client in your React application. Here's a complete example:
import { createAPIClient } from '../api/directus';
import { directusRequestFn } from '../api/directus/requestFn';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
const queryClient = new QueryClient();
// Initialize the API client with custom Directus requestFn
const api = createAPIClient({
requestFn: directusRequestFn,
queryClient,
baseUrl: 'https://your-directus-instance.com',
});
function FavoritesCount({ isAuthenticated }: { isAuthenticated: boolean }) {
const { data: count, isPending, error } = api.items.readItemsFavorites.useQuery(
{
query: {
limit: 0,
meta: 'filter_count',
fields: ['*', { picture: [{ file: ['*'] }] }],
filter: {
artwork: {
_nnull: true,
},
},
},
},
{
select: (data) => data?.meta?.filter_count ?? 0,
enabled: Boolean(isAuthenticated),
}
);
if (isPending) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Favorites count: {count}</div>;
}
export default function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
return (
<QueryClientProvider client={queryClient}>
<button onClick={() => setIsAuthenticated(!isAuthenticated)}>
Toggle Auth
</button>
<FavoritesCount isAuthenticated={isAuthenticated} />
</QueryClientProvider>
);
}
Key Features Demonstratedโ
- Type-safe Directus queries: The
queryparameter is fully typed with Directus's query syntax - Metadata support: Using
meta: 'filter_count'to get efficient count results - Data transformation: Using
selectto extract the count from the response - Conditional fetching: Using
enabledto control when the query executes - Error handling: Proper handling of loading and error states
Additional Configurationโ
Obtaining the OpenAPI Specificationโ
To get the OpenAPI specification from your Directus instance, you can use a script:
{
"scripts": {
"download-api-client:directus": "curl --fail --silent --show-error -o ./src/api/directus/openapi.json 'http://localhost:8055/server/specs/oas?access_token=admin' && prettier --write ./src/api/directus/openapi.json"
}
}
Replace http://localhost:8055 with your Directus instance URL and adjust the access token as needed.
Multiple APIsโ
If you're managing multiple APIs (e.g., Directus, Hono, and others), you can configure them all in the same apis.yaml file:
x-openapi-qraft:
plugin:
tanstack-query-react: true
openapi-typescript: true
apis:
directus:
root: src/api/directus/openapi.json
x-openapi-qraft:
output-dir: src/api/directus
# ... Directus-specific configuration
hono:
root: ./src/hono/openapi.json
x-openapi-qraft:
output-dir: src/hono
# ... Hono-specific configuration
Next Stepsโ
- Learn more about Redocly configuration
- Explore CLI options for advanced customization, including the
--operation-parameters-type-wrapperoption - Check out the Quick Start guide for general usage patterns