directus-extension-api-docs
v3.0.1
Published
directus extension for swagger interface and openapi including custom endpoints definitions // custom endpoint validations middleware based on openapi or zod
Maintainers
Readme
directus-extension-api-docs
Compatible with Directus
^9 || ^10 || ^11and both bundled and non-bundled endpoint extensions.
Release notes: see CHANGELOG.md.
Directus Extension providing:
- a Swagger UI interface (OpenAPI 3.x)
- an autogenerated OpenAPI specification file (merged core + your custom endpoints) -- including custom endpoint definitions
- optional validation middleware for your custom endpoints (based on merged OpenAPI spec). See details below
- optional Zod-first route definitions for type-safe validation + OpenAPI auto-generation (see Zod-first routes)

Contents
- Prerequisites
- Installation
- Configuration (optional)
- Definitions (optional) — YAML
- Validations (optional) — YAML-based runtime validation
- Zod-first routes (optional) — type-safe alternative
- YAML vs Zod
- Contributing · Reporting issues · License
Prerequisites
You must already have a Directus Node.js project running.
Ref: https://github.com/directus/directus
Installation
npm install directus-extension-api-docs- Swagger interface: by default
http://localhost:8055/api-docs - Openapi documentation: by default
http://localhost:8055/api-docs/oas
Configuration (optional)
To include your custom endpoints in the documentation, create an oasconfig.yaml file directly under the /extensions folder (recommended structure). Avoid placing it under /extensions/endpoints unless using Legacy mode.
Options:
docsPathoptional interface base path (default 'api-docs'). Resulting URLs:/<docsPath>and/<docsPath>/oas.infooptional openapi server info (default extract from package.json)tagsoptional openapi custom tags (will be merged with all standard and all customs tags)publishedTagsoptional if specified, only operations containing at least one of these tags are kept; all other paths and unused tags are removed.pathsoptional custom path objects keyed by full path (e.g./my-custom-path/my-endpoint). These are merged into Directus core paths.componentsoptional custom components (schemas, securitySchemes, etc.) shallow-merged over core components.useAuthenticationoptional (default false). When true,/api-docsand/api-docs/oasstay publicly reachable: without valid auth they list only anonymous/public paths (no custom endpoints); with auth they list only paths permitted to that user under Directus Access Policies and custom endpoints.
Example below:
docsPath: 'api-docs'
useAuthentication: true
info:
title: my-directus-bo
version: 1.5.0
description: my server description
tags:
- name: MyCustomTag
description: MyCustomTag description
publishedTags:
- MyCustomTag
components:
schemas:
UserId:
type: object
required:
- user_id
x-collection: directus_users
properties:
user_id:
description: Unique identifier for the user.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
type: string
Definitions (optional)
For each endpoint extension, you can define OpenAPI partials by adding an oas.yaml file in the root of that endpoint's folder.
Non-bundled extensions
Place the oas.yaml file directly in the extension folder:
- ./extensions/
─ oasconfig.yaml (optional)
- my-endpoint-extension/
- oas.yamlBundled extensions
For bundled extensions, place oas.yaml files in each sub-extension's folder under the src directory:
- ./extensions/
─ oasconfig.yaml (optional)
- my-bundle-extension/
- src/
- routes-endpoint/
- oas.yaml
- admin-endpoint/
- oas.yamlThis structure follows Directus's standard bundle architecture where each sub-extension (routes, endpoints, hooks, etc.) has its own folder under src/. The extension will automatically discover and merge all oas.yaml files from these subdirectories.
Mixed environments
Both bundled and non-bundled extensions can coexist in the same project. The extension will automatically detect and merge all oas.yaml files from both types.
Properties:
tagsoptional openapi custom tagspathsoptional openapi custom pathscomponentsoptional openapi custom components
Example below (./extensions/my-endpoint-extensions/oas.yaml):
tags:
- name: MyCustomTag2
description: MyCustomTag description2
paths:
"/my-custom-path/my-endpoint":
post:
security:
- Auth: [ ]
summary: Validate email
description: Validate email
tags:
- MyCustomTag2
- MyCustomTag
requestBody:
content:
application/json:
schema:
"$ref": "#/components/schemas/UserId"
responses:
'200':
description: Successful request
content:
application/json:
schema:
"$ref": "#/components/schemas/Users"
'401':
description: Unauthorized
content: {}
'422':
description: Unprocessable Entity
content: {}
'500':
description: Server Error
content: {}
components:
schemas:
Users:
type: object # ref to standard components declaring it empty
securitySchemes:
Auth:
in: header
name: Authorization
type: apiKeyLegacy mode
Configuration and definitions can also be managed in this legacy structure (still supported, but prefer the simplified root placement):
- ./extensions/
- endpoints/
- oasconfig.yaml
- my-endpoint-extensions/
- oas.yaml
- my-endpoint-extensions2/
- oas.yamlValidations (optional)
You can enable a request validation middleware based on your merged custom definitions.
Call the validate function inside your custom endpoint source (./extensions/my-endpoint-extensions/src/index.js).
Arguments: validate(router, services, schema, paths?). paths (optional array) lets you restrict validation to only specific path keys from oasconfig.yaml instead of all custom paths.
Example below:
const { validate } = require('directus-extension-api-docs')
export default {
id: 'my-custom-path',
handler: async (router, { services, getSchema }) => {
const schema = await getSchema();
await validate(router, services, schema); // Enable validator
router.post('/my-endpoint', async (req, res, next) => {
...
})
},
}Zod-first routes (optional)
An alternative ergonomic API: declare schemas and handlers together. The OpenAPI fragment is generated and merged into the same spec served at /api-docs/oas, and request validation runs automatically before each handler. Everything you need ships from this package — no need to import defineEndpoint from the Directus SDK.
const { defineEndpoint, z, registerSchema } = require('directus-extension-api-docs');
const UserId = registerSchema(
'UserId',
z.object({
user_id: z.string().uuid().openapi({ example: '63716273-0f29-4648-8a2a-2af2948f6f78' }),
}),
);
module.exports = defineEndpoint('my-custom-path', (route, { services, getSchema }) => {
route({
method: 'post',
path: '/my-endpoint',
tags: ['MyCustomTag'],
summary: 'Validate user id',
security: [{ Auth: [] }],
request: { body: UserId },
responses: {
200: { description: 'OK', schema: UserId },
401: { description: 'Unauthorized' },
},
handler: async (req, res) => {
// req.body is typed: { user_id: string }
res.json({ user_id: req.body.user_id });
},
});
});Notes:
- The OpenAPI prefix defaults to
/<id>so paths in/api-docs/oasmatch the URLs clients actually call (Directus mounts each endpoint extension under/<id>). services,getSchema,logger, ... are available in the setup closure and naturally accessible from each handler.- For finer control (e.g. mixing Zod and raw Express routes), the lower-level
defineRoute(router, config)is still exported.
Public exports (named exports of the package main, alongside validate):
| Export | Purpose |
| ----------------- | -------------------------------------------------------------------------------- |
| defineEndpoint | Declarative wrapper for a Directus endpoint extension built on Zod routes. |
| defineRoute | Lower-level route registration when you already have your own router. |
| registerSchema | Register a reusable Zod schema as components.schemas.<name> (emits $ref). |
| z | Re-exported zod already extended with .openapi() metadata. |
| zodValidator | The per-slot validation middleware used internally, for advanced use. |
Validation errors are returned as HTTP 400 with the same { message, errors[] } envelope used by express-openapi-validator, so existing API consumers don't need to change. Coexists with YAML definitions: pick whichever fits each endpoint.
YAML vs Zod
Both pipelines feed the same /api-docs/oas document and can be mixed per endpoint.
- YAML (
oasconfig.yaml+oas.yaml): preferred when the OpenAPI spec is the source of truth, or when you import an existing spec. Runtime validation is opt-in viavalidate(...). - Zod: preferred when you want one source of truth that also types
req.body/req.params/req.queryand validates by default. No separate YAML file to keep in sync.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md for setup, scripts, and commit conventions.
Reporting issues
Bugs and feature requests: github.com/sacconazzo/directus-extension-api-docs/issues.
For security vulnerabilities, please follow SECURITY.md instead of opening a public issue.
License
MIT © Giona Righini (sacconazzo)
