Overview
Directus is an open-source data platform that layers on top of any SQL database to provide a REST and GraphQL API, a no-code admin app, and granular role-based access control. Unlike traditional CMS platforms that create their own database schema, Directus mirrors your existing database structure and lets you manage it through an intuitive interface without altering the underlying tables.
Directus supports PostgreSQL, MySQL, MariaDB, SQLite, MS SQL, OracleDB, and CockroachDB. It provides realtime updates via WebSockets, a flows automation engine for building custom logic without code, file storage with image transformation, and an extensible architecture through custom hooks, endpoints, and modules.
Installation
npm
# Create a new Directus project
npm init directus-project@latest my-project
# Start the server
cd my-project
npx directus start
# Admin app: http://localhost:8055
Docker
# Quick start with Docker
docker run -d \
--name directus \
-p 8055:8055 \
-e SECRET="your-secret-key" \
-e ADMIN_EMAIL="admin@example.com" \
-e ADMIN_PASSWORD="password" \
-e DB_CLIENT="sqlite3" \
-e DB_FILENAME="/directus/database/data.db" \
-v directus_data:/directus/database \
-v directus_uploads:/directus/uploads \
directus/directus:latest
Docker Compose
version: "3"
services:
directus:
image: directus/directus:latest
ports:
- "8055:8055"
volumes:
- directus_uploads:/directus/uploads
- directus_extensions:/directus/extensions
environment:
SECRET: "your-random-secret"
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "password"
DB_CLIENT: "pg"
DB_HOST: "db"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "directus"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_DB: directus
POSTGRES_USER: directus
POSTGRES_PASSWORD: directus
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
directus_uploads:
directus_extensions:
REST API
CRUD Operations
# List items
curl "http://localhost:8055/items/articles" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Get single item
curl "http://localhost:8055/items/articles/1" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Create item
curl -X POST "http://localhost:8055/items/articles" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-d '{"title":"New Article","body":"Content here","status":"published"}'
# Update item
curl -X PATCH "http://localhost:8055/items/articles/1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-d '{"title":"Updated Title"}'
# Delete item
curl -X DELETE "http://localhost:8055/items/articles/1" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Bulk create
curl -X POST "http://localhost:8055/items/articles" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-d '[{"title":"Post 1"},{"title":"Post 2"}]'
Filtering and Querying
# Filter with operators
curl "http://localhost:8055/items/articles?filter[status][_eq]=published"
# Deep filter on relations
curl "http://localhost:8055/items/articles?filter[author][name][_contains]=Alice"
# Multiple filters (AND)
curl "http://localhost:8055/items/articles?filter[_and][0][status][_eq]=published&filter[_and][1][date][_gte]=2025-01-01"
# OR filters
curl "http://localhost:8055/items/articles?filter[_or][0][status][_eq]=published&filter[_or][1][status][_eq]=featured"
# Sort, limit, offset
curl "http://localhost:8055/items/articles?sort=-date_created&limit=10&offset=20"
# Select specific fields
curl "http://localhost:8055/items/articles?fields=id,title,slug,author.name"
# Deep populate relations
curl "http://localhost:8055/items/articles?fields=*,author.*,tags.tag_id.*"
# Search
curl "http://localhost:8055/items/articles?search=kubernetes"
# Aggregate
curl "http://localhost:8055/items/articles?aggregate[count]=*&groupBy[]=status"
Filter Operators
| Operator | Description |
|---|
_eq | Equal to |
_neq | Not equal to |
_lt / _lte | Less than / less than or equal |
_gt / _gte | Greater than / greater than or equal |
_in | In array |
_nin | Not in array |
_null | Is null |
_nnull | Is not null |
_contains | Contains substring |
_ncontains | Does not contain |
_starts_with | Starts with |
_ends_with | Ends with |
_between | Between two values |
_empty | Is empty |
_nempty | Is not empty |
JavaScript SDK
import { createDirectus, rest, authentication, readItems, createItem, updateItem, deleteItem, realtime } from "@directus/sdk"
const client = createDirectus("http://localhost:8055")
.with(authentication())
.with(rest())
// Authenticate
await client.login("user@example.com", "password")
// Read items
const articles = await client.request(
readItems("articles", {
filter: { status: { _eq: "published" } },
sort: ["-date_created"],
limit: 10,
fields: ["id", "title", "slug", { author: ["name", "avatar"] }],
})
)
// Create item
await client.request(createItem("articles", { title: "New Post", status: "draft" }))
// Update item
await client.request(updateItem("articles", 1, { status: "published" }))
// Delete item
await client.request(deleteItem("articles", 1))
Configuration
Environment Variables
# Database
DB_CLIENT=pg
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=directus
DB_USER=directus
DB_PASSWORD=password
# Security
SECRET=your-random-secret-key
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=7d
# Storage
STORAGE_LOCATIONS=local,s3
STORAGE_LOCAL_ROOT=./uploads
STORAGE_S3_DRIVER=s3
STORAGE_S3_KEY=your-access-key
STORAGE_S3_SECRET=your-secret-key
STORAGE_S3_BUCKET=your-bucket
STORAGE_S3_REGION=us-east-1
# Email
EMAIL_FROM=noreply@example.com
EMAIL_TRANSPORT=smtp
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=user
EMAIL_SMTP_PASSWORD=password
# CORS
CORS_ENABLED=true
CORS_ORIGIN=http://localhost:3000
# Cache
CACHE_ENABLED=true
CACHE_STORE=redis
CACHE_REDIS_HOST=127.0.0.1
CACHE_AUTO_PURGE=true
Advanced Usage
Flows (Automation)
Flows are created in the admin app under Settings > Flows. They consist of triggers and operations:
| Trigger | Description |
|---|
| Event Hook | Fires on CRUD events (items.create, items.update, etc.) |
| Schedule (Cron) | Runs on a schedule |
| Action | Manual trigger from the admin app |
| Webhook | Triggered by external HTTP request |
| Operation | Description |
|---|
| Send Email | Send an email notification |
| Send Webhook | Make an HTTP request |
| Run Script | Execute custom JavaScript |
| Create Data | Insert a record |
| Update Data | Modify a record |
| Read Data | Query records |
| Condition | Branch logic based on conditions |
| Log to Console | Log data for debugging |
Custom Extensions
# Create a custom endpoint
npx create-directus-extension@latest
# Select "endpoint" type
# Extension structure
extensions/
endpoints/
my-endpoint/
src/
index.ts
package.json
// extensions/endpoints/my-endpoint/src/index.ts
import { defineEndpoint } from "@directus/extensions-sdk"
export default defineEndpoint((router, context) => {
router.get("/", async (req, res) => {
const { services, getSchema } = context
const { ItemsService } = services
const schema = await getSchema()
const articlesService = new ItemsService("articles", { schema, accountability: req.accountability })
const articles = await articlesService.readByQuery({ limit: 10 })
res.json(articles)
})
})
Realtime (WebSockets)
const client = createDirectus("http://localhost:8055")
.with(authentication())
.with(realtime())
await client.connect()
// Subscribe to changes
const { subscription } = await client.subscribe("articles", {
event: "create",
query: { fields: ["id", "title", "status"] },
})
for await (const message of subscription) {
console.log("New article:", message)
}
Troubleshooting
| Issue | Solution |
|---|
| Admin app blank after upgrade | Run npx directus database migrate:latest then restart |
| Permission denied on API | Check role permissions in Settings > Access Control |
| Slow queries | Enable caching with CACHE_ENABLED=true; add database indexes |
| File uploads failing | Check storage configuration and disk permissions |
| WebSocket disconnects | Verify WEBSOCKETS_ENABLED=true; check proxy WebSocket support |
| Extension not loading | Place extensions in the extensions/ directory; restart Directus |
| Database migration errors | Back up data first; run npx directus database migrate:latest |
| CORS errors | Set CORS_ORIGIN to your frontend URL; ensure CORS_ENABLED=true |
| Memory issues | Increase Node.js heap: NODE_OPTIONS=--max-old-space-size=4096 |