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 addCreate a profile and optionally save a credential
d6s profile updateChange a profile's name, URL, or saved credential
d6s profile listList configured profile names and URLs
d6s profile testConnect with a profile or URL and print who you are on the instance
d6s profile removeRemove a profile and its saved credential
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

Install @directus/cli@12. It provides directus-cli and the equivalent short alias d6s.

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>]
FlagEffect
--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]
FlagEffect
--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
--yesSkip 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>]
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).

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]
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 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]
FlagEffect
--to <profile> (required)Target profile name
--mode <mode>add, merge, or mirror; changes what the preview plans for
--allow-driftPreview 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]
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-driftPush 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:

KeyMeaningDefault
profilesNamed instances and their URLs{}
directoryThe directory pulls write into and pushes read from"directus"
formatSync-file format; currently only "json""json"
projectsPer-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:

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, its credential resolves in order:

  1. A --token flag on profile add, profile update, or profile test. 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 static token or login session in ~/.directus/credentials.json, written readable only by you (mode 0600). Never consulted when CI is non-empty, except when its value is false.

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; on Directus 12.2.0 and later, 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 overwrites the sync files in its requested scope with the source's current state. Files outside that scope are left unchanged.
  2. 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.

PullSchema sync filesConfiguration sync files
pull --from stagingAll overwritten from sourceDefault set overwritten; users left unchanged
... --collections postsposts overwritten; all others left unchangedDefault set overwritten; users left unchanged
... --no-flowsAll overwritten from sourceOther defaults overwritten; flows, operations, and users left unchanged
... --no-translationsAll overwritten from sourceOther defaults overwritten; translations and users left unchanged
... --flowsAll overwritten (resource flags do not narrow Schema)Flows and operations overwritten; all others left unchanged
... --flows --no-schemaAll left unchangedFlows and operations overwritten; all others left unchanged
... --collections posts --no-flowsposts overwritten; all others left unchangedOther defaults overwritten; flows, operations, and users left unchanged

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 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:

ResourceMatched by
Roles, policies, flows, dashboards, and foldersname
Usersemail
Access rulesrole, user, and policy
Permissionspolicy, collection, and action
Operationsflow and key
Translationslanguage and key
SettingsThe single settings record
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:

✖ 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. 🐰