Reference

Every Environment Sync command and flag, the directus.config.json format, credential resolution, scope tables, push modes, deletion gates, and JSON report formats.

Everything on this page is lookup material. If you're learning the tool, start with the Quickstart and How It Works instead.

Commands

CommandWhat it does
d6s profile addAdd or update a profile (name + URL) and optionally save a credential
d6s profile testConnect with a profile and print who you are on the instance
d6s sync pullWrite a source instance's Schema and Configuration to sync files
d6s sync diffShow what a push would change on the target; applies nothing
d6s sync pushApply the sync files to a target instance
d6s syncInteractive wizard: prompts for source, target, project, and mode, then pulls and pushes

The CLI installs as directus-cli with d6s as an equivalent short alias.

Global flags, available on every command:

FlagEffect
--jsonOne machine-readable report on stdout; human status stays off stdout
--no-colorDisable colored output
--no-interactiveDisable 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>] [--yes]
FlagEffect
--url <url>Directus instance URL
--token <token>Static token to save to the credential store for this profile
--yesSkip the confirmation when repointing an existing profile to a new URL

Adding is an upsert: an existing name is updated. 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 test

d6s profile test <name>
FlagEffect
--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).

d6s sync pull

d6s sync pull --from <profile> [scope flags]
FlagEffect
--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-schemaSkip 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
--allEvery configuration resource, including users
--no-depsDo 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 --collections yourself.
  • A name the server didn't return. A --collections name absent from the returned Schema (usually a typo) is named in a warning. The partial sync files are still written, but never silently.

d6s sync diff

d6s sync diff --to <profile> [--mode <mode>] [--allow-version-drift]
FlagEffect
--to <profile> (required)Target profile name
--mode <mode>add, merge, or mirror; changes what the preview plans for
--allow-version-driftPreview despite a version mismatch (see the version 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-version-drift]
FlagEffect
--to <profile> (required)Target profile name
--mode <mode>add, merge (default), or mirror
--yesSkip the apply confirmation; never authorizes deletions
--dangerously-allow-deleteConsent to deletions; required for non-interactive mirror
--allow-version-driftPush despite a version mismatch (see the version rule)
--project <name>Project to sync (default: default)

directus.config.json

Created and updated by d6s profile add; found by walking up from the current directory, like git finds .git. It never contains credentials, so commit it. The full shape:

{
  "profiles": {
    "staging": { "url": "https://staging.example.com" },
    "production": { "url": "https://cms.example.com" }
  },
  "directory": "directus",
  "projects": {
    "default": {
      "schema": true,
      "collections": ["pages", "posts"],
      "resources": ["flows", "settings"],
      "mode": "merge"
    }
  }
}

Top-level keys:

KeyMeaningDefault
profilesNamed instances: { "url": "https://..." } per profile{}
directoryThe directory pulls write into and pushes read from"directus"
projectsPer-project sync options (see below){}

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:

KeyMeaning
schemafalse 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
collectionsSchema scope: only these collections
excludeCollectionsSchema scope: all collections except these
resourcesOnly these configuration resources
excludeResourcesThe default resources except these
modeDefault push mode for this project: add, merge, or mirror
depsfalse 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, the token resolves in order:

  1. A --token flag, on the two profile commands that accept one. The sync commands take no token flag.
  2. The DIRECTUS_<PROFILE>_TOKEN environment variable: the profile name uppercased, so production reads DIRECTUS_PRODUCTION_TOKEN. A .env file next to directus.config.json is loaded automatically without overriding real environment variables.
  3. The credential store at ~/.directus/credentials.json, written readable only by you (mode 0600). Never consulted when the CI environment variable is set.

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).

ResourceIn default pullSelect directlyAlso includesNotes
rolesYes--rolespolicies
policiesYes--policiesaccess, permissionsSee the warning below before selecting policies on their own.
accessYes, with roles and policiesGrants attached to users are dropped when users are out of scope; a mirror push does not delete them on the target.
permissionsYes, with policiesRecord counts are verified against the server. If the source hides records (unlicensed custom permission rules), the pull is incomplete.
flowsYes--flowsoperations
operationsYes, with flows
dashboardsYes--dashboardspanels
panelsYes, with dashboardsPanels have no field to match on, so a first push into a matching target can duplicate once; the ID map prevents repeats.
settingsYes--settingsA single record. License and AI credentials, branding images (logos, backgrounds, favicon), and the default storage folder are stripped.
foldersYes--foldersThe media-library folder tree. Distinct from collection folders (Data Studio sidebar groups), which sync as Schema.
translationsYes--translationsMatched by language and key; merge and mirror can update existing strings.
usersOpt-in--usersroles, policiesSecret fields (password, token, tfa_secret, and others) are stripped.
Select --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:

  1. A pull writes sync files only for what it fetched. Sync files for anything it did not fetch stay unchanged.
  2. A push only applies what is in the sync files. Work that never entered them cannot ship.

Written in this pull means the CLI fetched the current source state and wrote it to the corresponding sync files. Because writes are deterministic, an identical source state produces no git diff. Not written means the pull left those sync files unchanged on disk.

PullSchema sync filesConfiguration sync files
pull --from stagingAll written in this pullDefault set written; users not written
... --collections postsposts written; others not writtenDefault set written; users not written
... --no-flowsAll written in this pullFlows, operations, and users not written; other defaults written
... --no-translationsAll written in this pullTranslations and users not written; other defaults written
... --flowsAll written (resource flags do not narrow Schema)Flows and operations written; all others not written
... --flows --no-schemaNone writtenFlows and operations written; all others not written
... --collections posts --no-flowsposts written; others not writtenFlows, operations, and users not written; other defaults written

Push modes

ModeSchemaConfigurationDeletes?
addAdds and modifies, same as mergeCreates only; existing records are never updatedNo
merge (default)Adds and modifiesCreates and updatesNo
mirrorMay delete in scopeCreates, updates, and deletes records absent from the sync filesYes, 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:

ContextTo delete you must
Interactive terminalReview the plan naming the losses, then type the profile name (unless you passed --dangerously-allow-delete, which is the consent)
Non-interactive / CIPass --dangerously-allow-delete

--yes skips the ordinary confirmation prompt, but it never authorizes a deletion; that holds even if a supposedly additive 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 the commit-ready files; 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 an identifying field:

ResourceMatched by
Most resourcesname
Usersemail
Operationskey
Translationslanguage and key
PanelsNothing; 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:

✖ Ambiguous target matches:
  directus_roles source "Editor" — sr1 → one of t1, t2
  Run d6s sync push interactively once to choose, then commit the updated ID map.

The version rule

Schema changes require the Directus version recorded in the sync files and the target's version to match exactly, patch release included; the mismatch error names both versions. --allow-version-drift proceeds anyway with a warning; the CLI never translates schema between versions. Projects with "schema": false skip the check entirely, and a target whose version cannot be read is left to the server's own gate rather than refused.

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": ["directus_flows", "directus_roles"],
    "collections": 10,
    "records": 57,
    "files": 10,
    "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,
  "unresolved": 0,
  "schemaSkipped": false,
  "added": 1,
  "modified": 1,
  "deleted": 0,
  "data": {
    "mode": "merge",
    "source": "https://staging.example.com",
    "collections": {
      "directus_flows": {
        "existing": [],
        "new": ["f1"],
        "deleted": [],
        "mapped": {}
      }
    },
    "matched": 1,
    "ambiguous": 0,
    "unmatched": 1,
    "unchanged": 0,
    "incomplete": [],
    "skipped": false
  }
}

changes is true when a push would do anything, including when records are unresolved; added/modified/deleted count Schema items; data.collections is the target server's own per-collection dry-run answer.

d6s sync push --to production --yes --json reports the same shape as a diff, with applied (true when the push changed the target) in place of unresolved, and data.collections reflecting what the import actually did.

Failures put an error report on stdout:

{
  "kind": "ErrorReport",
  "formatVersion": 1,
  "error": {
    "code": "STATE",
    "message": "Version mismatch: the snapshot was pulled from Directus 11.2.0, but the target runs 11.2.5.",
    "hint": "..."
  }
}

The code is one of a small set of failure classes: USAGE (the command line needs fixing: a missing flag or missing consent), CONFIG (directus.config.json missing or invalid), 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). 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. --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. 🐰