A headless CMS and admin panel that wraps a SQL database you already own, generating a REST and GraphQL API and an editing interface from the existing schema.
Build me the admin and API layer I actually need instead of a hosted Directus: an editing interface over my existing SQL database, with permissions enforced at the API and a revision per record. Read this first: the product you are replacing is open source and self-hosting it is a real answer, particularly because generating an admin panel from an arbitrary schema is genuinely a lot of work. Build this when the schema is yours and stable — a generated interface over ten known tables is an afternoon each, and it will fit your data better than a generic one ever does. STACK - Node 20+ with Fastify, server-rendered HTML with a little vanilla JS - Your existing database through its own driver; SQLite through better-sqlite3 for this layer's own state - No client-side framework - Caddy in front THE DATA MODEL, MEANING THIS LAYER'S OWN - collections: id, table_name, label, singular, icon, sort_field, default_sort, is_hidden - fields: id, collection_id, column_name, label, kind, interface, options_json, position, is_readonly, is_required, validation_json, hidden_in_list - relations: id, from_collection, from_field, to_collection, kind — one-to-many, many-to-one, many-to-many with its join table - roles, users, permissions: role_id, collection, action, fields_json, filter_json — action is read, create, update or delete, and filter_json is a predicate joined into every query for that role - revisions: id, collection, record_id, actor_id, action, before_json, after_json, at — append-only, enforced by a trigger - activity: id, actor_id, action, collection, record_id, ip_hash, at - files: id, path, sha256, mime, bytes, width, height, title, alt, folder_id, uploaded_by, created_at - The configuration is introspected from the database on first run and then stored, so a column rename is a deliberate edit rather than a silent break INTROSPECTION - Read the schema: tables, columns, types, nullability, defaults, primary and foreign keys, unique constraints, check constraints - Map each column type to a default interface — text, number, boolean, date, select from an enum, relation from a foreign key - Every mapping is overridable and the override is stored. Guessing is a starting point, not an answer - Detect a change in the underlying schema on boot and report it rather than adapting silently PERMISSIONS, ENFORCED WHERE IT MATTERS - At the API layer, not in the interface. The interface hides what a role cannot do; the API refuses it - Per collection, per action, per field — a role can read a record but not its salary column, and that column is absent from the response rather than blanked in the browser - Row filters as predicates: a regional manager sees rows where region matches their own, expressed once and joined into every query for that role - Default deny. A new table is invisible until permission is granted - A page generated from the permissions table showing exactly what each role can do THE ADMIN INTERFACE - List: filters per field type, sort, pagination with a hard row limit, and a column set per role - Detail: fields in configured groups, related records inline, and the revision history beside them - Interfaces per type: plain text, rich text as Markdown, a code editor, date and time with a zone, a select, a many-to-one picker with search, a many-to-many list with add and remove, a file picker, a map if there are coordinates - Validation from the database's own constraints plus whatever is configured, applied on the server first - Keyboard navigation for the list, and it works on a phone - Dark and light REVISIONS - Every write records before and after values, the actor and the time - Revert a record to any earlier revision, which itself writes a new revision — history is never rewritten - A diff view between two revisions - This is the feature people name when asked why they use the product, and it is thirty lines plus a trigger THE API - REST per collection: list with filtering, field selection, relation expansion, sorting and pagination - Filters expressed in a small, explicitly parsed query language. Never anything that reaches the database as text a caller supplied - Tokens with role scopes; the same permission code path as the interface, not a parallel one - Webhooks on create, update and delete, signed and retried with backoff - A statement timeout and a row cap on everything FILES - Uploads streamed to disk, type from content, stored by hash outside the web root - Image transforms on demand with signed parameters and a cached result - Deletion is real, including derivatives, and is refused while a record still references the file OPERATIONS - .env: DATABASE_URL, DATABASE_PATH, STORAGE_PATH, BASE_URL, SESSION_SECRET, OIDC_*, HASH_SALT - Migrations for this layer's own tables on boot, each once - Nightly backup of both databases and the file store, restore script - Health endpoint that touches both databases WHAT MATTERS MOST Field-level permissions and revisions. Build the permission check into the single query path before any screen exists, and confirm a forbidden field is absent from the JSON rather than hidden in the page. An admin panel is a standing door into the whole database, and every shortcut taken here is a shortcut somebody else eventually walks through. Give me the repository, migrations, .env.example, an introspection run against a seed schema, and a README with deploy steps behind Caddy.
What you lose
- A complete admin interface generated from any SQL schema, with relations, file handling and revisions
- Granular role and permission rules enforced at the API layer rather than in your code
- A revision history per record, which is a lot of plumbing to reproduce
If you would rather not build
- Django admin, if the stack allows it
What it costs
read from their page 15 Aug 2026
| Plan | Billed monthly | Billed yearly | Last read |
|---|---|---|---|
| — | $99/mo | — | 15 Aug 2026 |
Their pricing page is where these came from. Seeing a different price? Tell us.
The escape hatch
open source · no votes, no paid placement
Refine
$0A React framework for admin panels, if you would rather write it than generate it.
refinedev/refinefree · open source
Why this verdict
our own opinion · changed only by a person
72/100
Verdict yes at 72. Generating an admin from a schema is a genuinely satisfying build; permissions and revisions are the parts to do carefully.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about Directus
answered from the record above
Is Directus free?
No — the plan we track is $99 a month. Directus Cloud at $99/month as an add-on to the free Core tier; the Team plan is $599/month billed monthly, $499 annually. Self-hosting the software is free.
Can you replace Directus by building your own?
YES. Replaceable in one session with an AI coding agent. Replacement score 72 out of 100, build time one session. Read what you lose before you decide.
How much does Directus cost?
$99 a month on Starter — $1,188 a year. Recorded 14 Aug 2026.
What do you lose by replacing Directus?
A complete admin interface generated from any SQL schema, with relations, file handling and revisions; Granular role and permission rules enforced at the API layer rather than in your code; A revision history per record, which is a lot of plumbing to reproduce. If any of those carry weight for you, keep paying.
Is there an open-source alternative to Directus?
Yes: Directus, Refine. The prompt on this page is for when you want it your way instead.
Related entries
same category first, most replaced first
Every week, something stops being worth paying for.
New verdicts, prices that moved, entries added. One email a week. Unsubscribe in one click. Nothing is being sent yet — your address is kept here, and the first issue is the first thing it is used for.
free forever · no tracking pixel · stored here, never passed to anyone

