Secrets & Limitations

How Environment Sync keeps secret values out of the files you commit, the one exception to review before publishing a repository, and what the tool deliberately does not do.

How secrets are handled

A pull writes real configuration records to sync files: settings, user accounts, flow definitions, and more. If any carried a secret value, it would land in JSON files you commit, and git history would keep it in every clone of the repository. Environment Sync strips secret values during pull:

  • Built-in secret fields (password hashes, tokens, 2FA seeds, license and AI keys) are always removed from the sync files.
  • Fields you created and marked concealed, hashed, or encrypted are removed too. Every pull that includes Configuration fetches the server's complete field list and drops any flagged value, printing a line naming each dropped field. A value missing from a sync file is protection, not data loss.

Because the field list is fetched separately from Schema, the check also covers pulls scoped to a few collections and pulls that skip Schema entirely. If the check itself cannot run, the pull stops rather than continue unprotected.

The field definition still syncs as Schema: the field and its concealed setting arrive on the target. Only the value stays behind; set the real secret on each instance directly.

Why strip instead of sync

The server never hands out the real value for these fields: concealed fields read back as **********, hashed fields as the hash. Writing that value to a sync file and pushing it would overwrite the target's working secret with a mask. Stripping protects both the repository and the target.

The one blind spot

Flow request headers are written verbatim. A secret pasted into free-form flow configuration (most commonly an Authorization header in a request operation) has no "this is secret" marker for the CLI to check, and legitimate headers have to sync. The pull warns you by operation name, and the value goes into the sync file as-is. Review those files before committing and before publishing a repository.

System collections that do not sync

None of these sync today. Some are shared configuration that a future release could take on; the rest are per-instance state that a sync should never touch.

CollectionWhy it doesn't sync
directus_presetsBookmarks, saved layouts, and Insights presets mix shared configuration with personal preference. A future release may sync the shared part.
directus_extensionsThe enabled/disabled state of installed extensions is tied to what is physically deployed on each instance. Deploy extensions to the target before pushing schema or flows that depend on them; the push itself does not warn.
directus_filesFile-interface fields sync as Schema; file records and the binaries behind them are their own workstream.
directus_commentsContent comments; per-instance state.
directus_activityThe audit log; per-instance state.
directus_revisionsChange history; per-instance state.
directus_versionsContent-versioning drafts; per-instance state.
directus_notificationsUser notifications; per-instance state.
directus_sharesPublic share links; per-instance state.
directus_sessionsActive login sessions; per-instance state.
directus_migrationsThe database migration ledger. The CLI never runs migrations or changes the Directus version.
directus_webhooksDeprecated in Directus; superseded by flows.

What it does not do

Won'tBecause / instead
Sync records in your own collectionsEnvironment Sync moves the shape of a project and its configuration. Content sync is deferred to a future release.
Undo a pushRevert the commit and push again; removals need mirror. See Roll back a bad push. Only a database backup covers content.
Auto-expand a collection-scoped pullA collection scope pulls exactly what you name. Dangling references produce a warning; widen --collections yourself. Resource dependencies are handled separately and included by default.
Translate schema across versionsA version mismatch refuses the command; --allow-drift bypasses the compatibility gate but converts nothing.
Translate schema across database vendorsA vendor mismatch refuses the command; --allow-drift bypasses the same gate but does not rewrite vendor-specific column types.
Wrap Schema and Configuration in one transactionSchema applies first, then Configuration. A failed import re-runs Configuration alone; see How It Works.
Select individual recordsResource selection is by type (--roles), never by record.
Model code-firstModel in the Data Studio and pull. Don't hand-author the JSON files.
Seal the Configuration plan against target driftOnly the Schema apply is sealed against the target changing between preview and apply. The Configuration preview is the server's own dry-run answer, but it is advisory.

Known limitations

  • Unlicensed custom permission rules are unavailable to pull. On an instance without a license, the API hides custom permission rules. The pull detects the missing records and marks the result incomplete: merge and add push normally, while mirror refuses. License the source instance to include them.
  • Users without an email address fail import. The server's import validation rejects a user with no email, so a --users push containing one fails before any Configuration changes apply. Give the account an email on the source, or remove it, first.
  • Panels can duplicate on a first sync into a look-alike target. Panels have no name to match on, so pushing into a target that already holds equivalent panels (seeded from the same template) can create them a second time. The ID map prevents repeats after that first push.
  • A mirror push of users does not protect your own account. If users are in the sync files and the account the push authenticates with is absent from them, a mirror push orders its deletion like any other record; the CLI has no self-protection, and whether the server refuses is up to the server. When you sync users, make sure the sync files include the accounts your pushes run as.
  • A Configuration diff can leave harmless gaps in PostgreSQL integer IDs. The target server dry-runs the import and rolls it back, but PostgreSQL sequences do not roll back. No records are created or changed; only a sequence such as the one for directus_permissions can advance.

Get once-a-month release notes & real‑world code tips...no fluff. 🐰