Reference
Everything on this page is lookup material. If you're learning the tool, start with the Quickstart and How It Works instead.
Commands
| Command | What it does |
|---|---|
d6s profile add | Create a profile and optionally save a credential |
d6s profile update | Change a profile's name, URL, or saved credential |
d6s profile list | List configured profile names and URLs |
d6s profile test | Connect with a profile or URL and print who you are on the instance |
d6s profile remove | Remove a profile and its saved credential |
d6s sync pull | Write a source instance's Schema and Configuration to sync files |
d6s sync diff | Show what a push would change on the target; applies nothing |
d6s sync push | Apply the sync files to a target instance |
d6s sync | Interactive wizard: prompts for source, target, project, and mode, then pulls and pushes |
Install @directus/cli@12. It provides directus-cli and the equivalent short alias d6s.
Global flags, available on every command:
| Flag | Effect |
|---|---|
--json | One machine-readable report on stdout; human status stays off stdout |
--no-color | Disable colored output |
--no-interactive | Disable prompts; behave as in CI |
--config <path> | Path to directus.config.json (default: found by walking up from the current directory) |
d6s profile add
d6s profile add [name] [--url <url>] [--token <token>]
| Flag | Effect |
|---|---|
--url <url> | Directus instance URL |
--token <token> | Static token to save to the credential store for this profile |
An existing profile name is refused; use profile update to change it. Run without arguments for prompts, which also offer to save a credential (paste a static token, or log in with email and password to save a session; saved sessions refresh themselves before they expire). Profile names use letters, numbers, and underscores. URLs with embedded credentials, query strings, or fragments are refused because the URL is stored in project configuration.
d6s profile update
d6s profile update [name] [--name <name>] [--url <url>] [--token <token>] [--yes]
| Flag | Effect |
|---|---|
--name <name> | Rename the profile; run this separately from URL or credential changes |
--url <url> | Point the profile at a new Directus instance; keeps the current URL when omitted |
--token <token> | Replace the saved credential with a static token |
--yes | Skip confirmation when changing the profile name or URL |
Renaming moves the saved credential to the new profile name and changes the environment variable the profile reads. Changing the URL clears the credential saved for the old URL; a DIRECTUS_<PROFILE>_TOKEN environment variable follows the profile to the new URL. The command states these effects before asking for confirmation.
d6s profile list
d6s profile list
Prints each configured profile name and URL. Credentials are never shown.
d6s profile test
d6s profile test [name] [--url <url>] [--token <token>]
| Flag | Effect |
|---|---|
--url <url> | Test a URL directly, without a profile or config file |
--token <token> | Override the resolved token |
Connects and prints who the credential authenticates as:
◇ Authenticated to https://cms.example.com as Admin User (Administrator).
Pass either a profile name or --url, never both. Without a stored or environment credential, an interactive run asks for one; a non-interactive run tells you which token flag or environment variable to set.
d6s profile remove
d6s profile remove [name] [--yes]
Removes the profile and clears its saved credential after confirmation. --yes skips that confirmation; non-interactive runs require it.
d6s sync pull
d6s sync pull --from <profile> [scope flags]
| Flag | Effect |
|---|---|
--from <profile> (required) | Source profile name |
--collections <list> | Schema scope: only these collections (comma-separated) |
--exclude-collections <list> | Schema scope: all collections except these |
--no-schema | Skip Schema entirely; Configuration resources only |
--<resource> | Select only the named resources, e.g. --flows --roles (plus their dependencies) |
--no-<resource> | Keep the default resource set but exclude one, e.g. --no-flows |
--all | Every configuration resource, including users |
--no-deps | Do not add prerequisites; resources owned by a selected parent remain included |
--project <name> | Project to sync (default: default) |
A bare pull includes every selectable resource except users. This means translations sync by default. Pass --no-translations to exclude them, or --translations to pull only translations.
The selectable resources are roles, policies, flows, dashboards, settings, folders, users, and translations. The CLI automatically includes access, permissions, operations, and panels with their parent resources (see the dependency table). Positive selection (--flows) cannot be combined with --all or with --no- flags. Resource selection never narrows the schema; the two axes are scoped independently.
Two warnings a scoped pull can raise, neither of which widens the scope for you:
- Out-of-scope references. The scoped sync files point at something you omitted (a relation target, a group parent, a many-to-any collection). Pushing those files to a fresh target can fail; add the missing collections to
--collectionsyourself. - A name the server didn't return. A
--collectionsname absent from the returned Schema (usually a typo) is named in a warning. The files for the rest of the requested scope are still overwritten, but never silently.
d6s sync diff
d6s sync diff --to <profile> [--mode <mode>] [--allow-drift]
| Flag | Effect |
|---|---|
--to <profile> (required) | Target profile name |
--mode <mode> | add, merge, or mirror; changes what the preview plans for |
--allow-drift | Preview despite a Directus version or database vendor mismatch (see the compatibility rule) |
--project <name> | Project to sync (default: default) |
Applies nothing, and exits 0 whether or not differences exist; automation reads the report's changes field.
d6s sync push
d6s sync push --to <profile> [--mode <mode>] [--yes] [--dangerously-allow-delete] [--allow-drift]
| Flag | Effect |
|---|---|
--to <profile> (required) | Target profile name |
--mode <mode> | add, merge (default), or mirror |
--yes | Skip the apply confirmation; never authorizes deletions |
--dangerously-allow-delete | Consent to deletions; required for non-interactive mirror |
--allow-drift | Push despite a Directus version or database vendor mismatch (see the compatibility rule) |
--project <name> | Project to sync (default: default) |
directus.config.json
Created by d6s profile add and changed by d6s profile update; found by walking up from the current directory, like git finds .git. It never contains credentials, so commit it. This example uses include scopes; each axis can use an exclude scope instead:
{
"profiles": {
"staging": { "url": "https://staging.example.com", "auth": { "type": "token" } },
"production": { "url": "https://cms.example.com", "auth": { "type": "token" } }
},
"directory": "directus",
"format": "json",
"projects": {
"default": {
"schema": true,
"collections": ["pages", "posts"],
"resources": ["flows", "settings"],
"mode": "merge"
}
}
}
Top-level keys:
| Key | Meaning | Default |
|---|---|---|
profiles | Named instances and their URLs | {} |
directory | The directory pulls write into and pushes read from | "directus" |
format | Sync-file format; currently only "json" | "json" |
projects | Per-project sync options (see below) | {} |
auth.type is currently always "token". The static token or saved login session itself stays outside this file.
Per-project keys, all optional. A project is a named slice of the sync with its own subdirectory (<directory>/<project>/); the default project exists without being declared. Flags on the command line override these per run:
| Key | Meaning |
|---|---|
schema | false makes this a configuration-only project: pull, diff, and push never touch schema, and reports say schemaSkipped so automation can tell a skipped phase from a matching one |
collections | Schema scope: only these collections |
excludeCollections | Schema scope: all collections except these |
resources | Only these configuration resources |
excludeResources | The default resources except these |
mode | Default push mode for this project: add, merge, or mirror |
deps | false skips pulling selected resources' dependencies |
"schema": false combined with a collections scope is refused as a contradiction, as is setting both an include and an exclude list for the same axis. Project names use letters, numbers, hyphens, and underscores.
Credentials
When a command authenticates a profile, its credential resolves in order:
- A
--tokenflag onprofile add,profile update, orprofile test. The sync commands take no token flag. - The
DIRECTUS_<PROFILE>_TOKENenvironment variable: the profile name uppercased, soproductionreadsDIRECTUS_PRODUCTION_TOKEN. A.envfile next todirectus.config.jsonis loaded automatically without overriding real environment variables. - The static token or login session in
~/.directus/credentials.json, written readable only by you (mode0600). Never consulted whenCIis non-empty, except when its value isfalse.
Use an admin credential. The Schema endpoints and the batch import the CLI relies on are admin-only on the server; the CLI does not check privileges up front, so a non-admin token fails with an authentication error or produces an incomplete pull that the completeness checks then flag.
Configuration resources
Resource selection follows a dependency graph: selecting a resource pulls in what it needs (unless --no-deps).
| Resource | In default pull | Select directly | Also includes | Notes |
|---|---|---|---|---|
roles | Yes | --roles | policies | |
policies | Yes | --policies | access, permissions | See the warning below before selecting policies on their own. |
access | Yes, with roles and policies | — | — | Grants attached to users are dropped when users are out of scope; a mirror push does not delete them on the target. |
permissions | Yes, with policies | — | — | Record counts are verified against the server. If the source hides records (unlicensed custom permission rules), the pull is incomplete. |
flows | Yes | --flows | operations | |
operations | Yes, with flows | — | — | |
dashboards | Yes | --dashboards | panels | |
panels | Yes, with dashboards | — | — | Panels have no field to match on, so a first push into a matching target can duplicate once; the ID map prevents repeats. |
settings | Yes | --settings | — | A single record. License and AI credentials, branding images (logos, backgrounds, favicon), and the default storage folder are stripped. |
folders | Yes | --folders | — | The media-library folder tree. Distinct from collection folders (Data Studio sidebar groups), which sync as Schema. |
translations | Yes | --translations | — | Matched by language and key; on Directus 12.2.0 and later, merge and mirror can update existing strings. |
users | Opt-in | --users | roles, policies | Secret fields (password, token, tfa_secret, and others) are stripped. |
--roles, not --policies alone
A selection that pulls policies without roles is not independently pushable when access records reference roles. With no roles in scope, a push to a fresh target fails. Select --roles instead; it includes policies and their dependent resources too.What a pull touches
Two rules govern every pull, scoped or not:
- A pull overwrites the sync files in its requested scope with the source's current state. Files outside that scope are left unchanged.
- A push only applies what is in the sync files. Work that never entered them cannot ship.
Overwritten from source means the CLI replaced the files in scope with the current source state. Because output is deterministic, an identical source state produces byte-identical files and no git diff. Left unchanged means the pull did not touch those files.
| Pull | Schema sync files | Configuration sync files |
|---|---|---|
pull --from staging | All overwritten from source | Default set overwritten; users left unchanged |
... --collections posts | posts overwritten; all others left unchanged | Default set overwritten; users left unchanged |
... --no-flows | All overwritten from source | Other defaults overwritten; flows, operations, and users left unchanged |
... --no-translations | All overwritten from source | Other defaults overwritten; translations and users left unchanged |
... --flows | All overwritten (resource flags do not narrow Schema) | Flows and operations overwritten; all others left unchanged |
... --flows --no-schema | All left unchanged | Flows and operations overwritten; all others left unchanged |
... --collections posts --no-flows | posts overwritten; all others left unchanged | Other defaults overwritten; flows, operations, and users left unchanged |
Push modes
| Mode | Schema | Configuration | Deletes? |
|---|---|---|---|
add | Adds and modifies, same as merge | Creates only; existing records are never updated | No |
merge (default) | Adds and modifies | Creates and updates | No |
mirror | May delete in scope | Creates, updates, and deletes records absent from the sync files | Yes, gated |
A mirror push deletes only within what the sync files cover: a pull scoped to some collections can delete fields inside those collections, never a collection it doesn't contain. A pull the source itself left incomplete (hidden permission records) is refused at mirror push outright.
Deletion gates
Only mirror deletes, and deleting always requires its own explicit consent:
| Context | To delete you must |
|---|---|
| Interactive terminal | Review the plan naming the losses, then type the profile name (unless you passed --dangerously-allow-delete, which is the consent) |
| Non-interactive / CI | Pass --dangerously-allow-delete |
--yes skips the ordinary confirmation prompt, but it never authorizes a deletion; that holds even if a non-deleting push unexpectedly carries one. A mirror push in CI without --dangerously-allow-delete refuses before changing anything:
✖ Refusing mirror mode in a non-interactive context without --dangerously-allow-delete.
mirror can delete schema and configuration records absent from ./directus/default; pass --dangerously-allow-delete to consent, or use --mode merge.
Record identity
Records are matched across instances first by the ID map (<directory>/<project>/id_map.json), then by identifying fields:
| Resource | Matched by |
|---|---|
| Roles, policies, flows, dashboards, and folders | name |
| Users | email |
| Access rules | role, user, and policy |
| Permissions | policy, collection, and action |
| Operations | flow and key |
| Translations | language and key |
| Settings | The single settings record |
| Panels | Nothing; ID map only |
The ID map is keyed internally by source and target instance URL, so one file serves any number of targets without conflicts. Commit it whenever a push changes it. An ambiguous match (two candidates) prompts interactively and refuses non-interactively:
✖ Push refused: 1 target match needs a choice.
directus_roles: ./directus/default contains 1 role named "Editor".
production — https://cms.example.com contains 2 matching roles.
Run d6s sync push interactively once to choose, then commit the updated ID map.
The compatibility rule
Environment Sync requires Directus 12.2.0 or later. Schema comparison requires the version recorded in the sync files and the target's version to match exactly, patch release included. The target server also requires the snapshot's database vendor to match its own. The CLI names both versions when it can detect a version mismatch; it cannot pre-check the target vendor, so it translates the server's refusal into an incompatible-snapshot error and keeps the server's reason as the detail.
--allow-drift sends the server's force bypass for either mismatch and prints a ▲ Compatibility check bypassed warning. It does not translate schema between Directus versions or database vendors. Projects with "schema": false skip the check entirely. When the target version cannot be read, the server makes the compatibility decision.
JSON reports
With --json, stdout carries exactly one report per command; warnings still go to stderr so logs keep them while stdout stays parseable. Reports are emitted as a single line; they're formatted here for readability.
d6s sync pull --from staging --json:
{
"kind": "PullReport",
"formatVersion": 1,
"ok": true,
"source": "https://staging.example.com",
"profile": "staging",
"project": "default",
"schemaSkipped": false,
"dir": "directus/default/schema",
"collections": 12,
"fields": 87,
"systemFields": 2,
"relations": 14,
"files": 13,
"removed": [],
"scope": null,
"data": {
"resources": [
"access",
"folders",
"operations",
"flows",
"panels",
"dashboards",
"permissions",
"policies",
"roles",
"settings",
"translations"
],
"collections": [
"directus_access",
"directus_folders",
"directus_operations",
"directus_flows",
"directus_panels",
"directus_dashboards",
"directus_permissions",
"directus_policies",
"directus_roles",
"directus_settings",
"directus_translations"
],
"recordCount": 57,
"collectionCount": 11,
"fileCount": 12,
"removed": [],
"incomplete": []
}
}
The Schema counters (collections through files) are null when the Schema phase is skipped; scope echoes a --collections/--exclude-collections scope; data.incomplete names resources whose pull the source cut short. The JSON API keeps data as its compatibility field name; it contains the Configuration report.
d6s sync diff --to production --json:
{
"kind": "DiffReport",
"formatVersion": 1,
"ok": true,
"target": "https://cms.example.com",
"profile": "production",
"project": "default",
"mode": "merge",
"changes": true,
"schemaSkipped": false,
"added": 1,
"modified": 1,
"deleted": 0,
"hash": "8a265a26cce1",
"data": {
"mode": "merge",
"source": "https://staging.example.com",
"resultsByCollection": {
"directus_flows": {
"existing": [],
"new": ["f1"],
"deleted": [],
"mapped": {}
}
},
"reconciliation": {
"matched": 1,
"unmatched": 1,
"ambiguous": 0,
"dependent": 0
},
"unchanged": 0,
"incomplete": [],
"skipped": false
}
}
changes is true when a push would do anything, including when data.reconciliation.ambiguous is non-zero. added/modified/deleted count Schema items. data.resultsByCollection is the target server's own per-collection dry-run answer. Reconciliation separates records that matched, have no match, need a choice, or depend on a choice; unchanged counts matched records whose synced values already agree.
d6s sync push --to production --yes --json reports the same top-level shape and adds applied, which is true when the push changed the target. Its data.resultsByCollection reflects what the import actually did; data.reconciliation and data.unchanged are null because push reports applied results rather than diff-only comparison counts.
Failures put an error report on stdout:
{
"kind": "ErrorReport",
"formatVersion": 1,
"error": {
"code": "STATE",
"message": "Version mismatch: the snapshot was pulled from Directus 12.1.1, but the target runs 12.2.0.",
"hint": "..."
}
}
The code is one of a small set of failure classes: USAGE (the command line needs fixing: a missing flag or missing consent), UNKNOWN_COMMAND, CONFIG (saved configuration is missing, invalid, or conflicts with the request), AUTH (the credential was rejected), HTTP (the instance could not be reached or returned an error), STATE (the sync files and the instance disagree: version mismatch, changed target Schema, incomplete pull), or UNKNOWN. Exit codes are 0 for success and 1 for every failure; the code string is the finer-grained signal.
Output conventions
Human-readable status lines go to stderr, prefixed ● (info), ◇ (success), ▲ (warning), or ✖ (error, with its hint indented beneath). Command results and plan lines go to stdout: + marks an addition, ~ a modification, and ✖ DELETE a deletion, with Configuration plans summarized per collection as +N new ~N updated ✖N deleted. Legacy Windows consoles use the ASCII equivalents i, +, !, x, and x DELETE. --no-color disables coloring; --json replaces stdout output with the report while warnings stay on stderr.
Get once-a-month release notes & real‑world code tips...no fluff. 🐰