Sync
Synchronizing folders & git repositories to a Windmill instance is made easy
using the wmill CLI. Syncing operations are behind the wmill sync subcommand.
The CLI supports workspace-specific items that allow different versions of resources and variables per workspace (dev / staging / prod).
Having a Windmill instance synchronized to folders & git repositories allows for local development through the VS Code extension.
Syncing is done using wmill sync pull & wmill sync push.
Syncing is a one-off operation with no state maintained. It will override any item that is within the scope: remove those that are in the target and not in the source, and it will create new items that are in source but not in target. It is a dangerous operation, that we recommend doing on a version-controlled folder to be able to revert any undesired changes made by mistake if necessary. It will however, show you the list of changes and ask you for confirmation before applying them.
To specify the scopes of files, you should modify the includes and excludes (array of path matchers) part of the wmill.yaml file generated with wmill init. We recommend looking at all the settings of the wmill.yaml file to understand the different options available.
Those are the default ones:
defaultTs: bun
includes:
- f/**
excludes: []
codebases: []
skipVariables: true
skipResources: true
skipResourceTypes: true
skipSecrets: true
includeSchedules: false
includeTriggers: false
See more details in wmill.yaml.
Note that here, the variables, resources, secrets, schedules, and triggers are all skipped by default. You can change that by setting the corresponding options.
When pulling, the source is the remote workspace and the target is the local folder, and when pushing, the source is the local folder and the target is the remote workspace.
To add a remote workspace, use wmill workspace add, and to switch workspace after having added multiple, use wmill workspace switch <source_workspace_id>. The workspaces can be on completely different instances.
wmill workspace switch <source_workspace_id>
wmill sync pull
wmill workspace switch <target_workspace_id>
wmill sync push
It can be used for bi-directional sync using Windmill EE and Git sync.
Pulling
wmill sync pull will simply pull all files from the currently
selected workspace and store
them in the current folder. Overwrites will not prompt the user. Make sure you
are in the correct folder or you may loose data.
Pushing
wmill sync push will simply push all local files to the currently
selected workspace, creating or
updating the remote equivalents.
Pull API
The wmill sync pull command is used to pull remote changes and apply them locally. It synchronizes the local workspace with the remote workspace by downloading any remote changes and updating the corresponding local files.
wmill sync pull [options]
Options
| Option | Parameter | Description |
|---|---|---|
-h, --help | None | Show help options. |
--yes | None | Pull without needing confirmation. The command proceeds automatically without user intervention. |
--plain-secrets | None | Pull secrets as plain text. Secrets are downloaded without encryption or obfuscation. |
--json | None | Use JSON instead of YAML. The downloaded files are in JSON format instead of YAML. |
--workspace | <workspace> | Select the target workspace. Matches a key in workspaces: (applying its overrides / specificItems) and falls back to a local profile name if no entry matches. |
--debug, --verbose | None | Show debug/verbose logs. |
--show-diffs | None | Show diff information when syncing (may show sensitive information). |
--token | <token> | Specify an API token. This will override any stored token. |
--base-url | <baseUrl> | Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace will be used. |
--skip-variables | None | Skip syncing variables (including secrets). |
--skip-secrets | None | Skip syncing only secret variables. |
--skip-resources | None | Skip syncing resources. |
--include-schedules | None | Include syncing schedules. |
--include-users | None | Include syncing users. |
--include-groups | None | Include syncing groups. |
--include-triggers | None | Include syncing triggers (HTTP routes, WebSocket, Postgres, Kafka, NATS, SQS, GCP Pub/Sub, MQTT). |
--include-settings | None | Include syncing workspace settings. |
--include-key | None | Include workspace encryption key. |
--skip-branch-validation | None | Skip git branch validation and prompts. Useful for temporary feature branches that don't need to be added to wmill.yaml. |
--branch | <branch> | Deprecated — use --workspace instead. Overrides the detected git branch when resolving a workspaces: entry. |
--env | <env> | Deprecated — use --workspace instead. Kept for single-branch setups that used the old environments: key. |
-i, --includes | <patterns> | Comma-separated patterns to specify which files to take into account (among files compatible with Windmill). Overrides wmill.yaml includes. Patterns can include * (any string until '/') and ** (any string). |
-e, --excludes | <patterns> | Comma-separated patterns to specify which files to NOT take into account. Overrides wmill.yaml excludes. |
--extra-includes | <patterns> | Comma-separated patterns to specify which files to take into account (among files compatible with Windmill). Useful to still take wmill.yaml into account and act as a second pattern to satisfy. |
Push API
The wmill sync push command is used to push local changes and apply them remotely. It synchronizes the remote workspace with the local workspace by uploading any local changes and updating the corresponding remote files.
wmill sync push [options]
Options
| Option | Parameter | Description |
|---|---|---|
-h, --help | None | Show help options. |
--workspace | <workspace> | Specify the target workspace. This overrides the default workspace. |
--debug, --verbose | None | Show debug/verbose logs. |
--show-diffs | None | Show diff information when syncing (may show sensitive information). |
--token | <token> | Specify an API token. This will override any stored token. |
--base-url | <baseUrl> | Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace will be used. |
--yes | None | Push without needing confirmation. |
--plain-secrets | None | Push secrets as plain text. |
--json | None | Use JSON instead of YAML. |
--skip-variables | None | Skip syncing variables (including secrets). |
--skip-secrets | None | Skip syncing only secret variables. |
--skip-resources | None | Skip syncing resources. |
--include-schedules | None | Include syncing schedules. |
--include-users | None | Include syncing users. |
--include-groups | None | Include syncing groups. |
--include-triggers | None | Include syncing triggers (HTTP routes, WebSocket, Postgres, Kafka, NATS, SQS, GCP Pub/Sub, MQTT). |
--include-settings | None | Include syncing workspace settings. |
--include-key | None | Include workspace encryption key. |
--skip-branch-validation | None | Skip git branch validation and prompts. Useful for temporary feature branches that don't need to be added to wmill.yaml. |
--branch | <branch> | Deprecated — use --workspace instead. Overrides the detected git branch when resolving a workspaces: entry. |
--env | <env> | Deprecated — use --workspace instead. Kept for single-branch setups that used the old environments: key. |
-i, --includes | <patterns> | Comma-separated patterns to specify which files to take into account (among files compatible with Windmill). Patterns can include * (any string until '/') and ** (any string). |
-e, --excludes | <patterns> | Comma-separated patterns to specify which files to NOT take into account. |
--extra-includes | <patterns> | Comma-separated patterns to specify which files to take into account (among files compatible with Windmill). Useful to still take wmill.yaml into account and act as a second pattern to satisfy. |
--message | <message> | Include a message that will be added to all scripts/flows/apps updated during this push. |
wmill.yaml
Note that you can set the default TypeScript language and explicitly exclude (or include) specific files or folders to be taken into account with a wmill.yaml file.
Generating and inspecting the config
wmill init generates a fully commented wmill.yaml with every option documented inline and grouped by category. Advanced / opt-in options are included as commented-out examples, so you can discover and enable them without having to look them up.
wmill config prints a structured reference table of all config options with their types, defaults, and descriptions. Pair it with --json for programmatic consumption (for example, from an AI agent that edits wmill.yaml).
wmill config # human-readable reference
wmill config --json # JSON reference, one entry per option
A JSON Schema is also committed at cli/wmill.schema.json for editor autocomplete and validation of your wmill.yaml.
Non-dotted paths (nonDottedPaths)
By default, local files for flows, apps, and raw apps are stored under folders named <path>__flow/, <path>__app/, and <path>__raw_app/ (using __flow/__app/__raw_app suffixes rather than dotted extensions). This avoids filesystem edge-cases on Windows and with tooling that assumes a .flow extension means a file.
# wmill.yaml
nonDottedPaths: true # default for new workspaces created with `wmill init --use-default`
Set nonDottedPaths: false to fall back to legacy .flow/.app/.raw_app folder names.
Basic configuration
defaultTs: bun # TypeScript runtime: 'bun' or 'deno'
# File path filtering
includes:
- f/** # Include patterns (glob format) [Use - "**" to include everything]
excludes: [] # Exclude patterns
extraIncludes: [] # Additional include patterns
# Codebase bundling configuration
codebases: []
# Skip flags (what to exclude from sync)
skipVariables: false
skipResources: false
skipResourceTypes: false
skipSecrets: true # Secrets are skipped by default for security
skipScripts: false
skipFlows: false
skipApps: false
skipFolders: false
# Include flags (what to explicitly include)
includeSchedules: false
includeTriggers: false
includeUsers: false
includeGroups: false
includeSettings: false
includeKey: false
# Git branch validation
skipBranchValidation: false # Skip validation for feature branches
Workspaces
Multi-target setups live under a single workspaces key in wmill.yaml. Each entry maps a
human-friendly workspace name to a Windmill instance, optional per-workspace sync overrides,
and optional workspace-specific items.
workspaces:
staging:
baseUrl: https://staging.windmill.dev
# workspaceId defaults to "staging" (the key)
# gitBranch defaults to "staging" (the key)
overrides:
includeSchedules: true
production:
baseUrl: https://app.windmill.dev
workspaceId: prod-ws # explicit: differs from the key
gitBranch: main # explicit: maps to git branch "main"
overrides:
skipSecrets: false
promotionOverrides: # applied when pushing with --promotion
skipApps: true
skipFlows: true
specificItems: # workspace-specific items (see below)
resources:
- "u/alex/prod_*"
variables:
- "u/alex/env_*"
# Items that are workspace-specific across ALL workspaces
commonSpecificItems:
resources:
- "u/alex/config/**"
variables:
- "u/alex/database_*"
folders:
- "f/env_*"
settings: true
Key properties:
- Key = workspace name. It's a human-friendly identifier, not the git branch.
workspaceIddefaults to the key — only set it when the Windmill workspace id differs from the name.gitBranchdefaults to the key — only set it when the git branch name differs.baseUrlpoints at the Windmill instance; the CLI resolves it to a local profile (baseUrl + workspaceId match), so the samewmill.yamlworks on any machine as long as a matching profile is logged in.overridesoverride any top-level sync option when this workspace is the current one.promotionOverridesapply when pushing to this workspace with--promotion.specificItemsdeclare items that are stored per-workspace on disk (see workspace-specific items).
Selecting a workspace
The CLI picks the active workspace in this order:
--workspace <name>— explicit override, matches a key inworkspaces:(or a local profile name as a fallback).- Current git branch — the first entry whose effective
gitBranchmatchesgit rev-parse --abbrev-ref HEAD. - Local profile — the active profile set via
wmill workspace switch(used when noworkspaces:entry matches).
All sync commands (wmill sync pull, wmill sync push, wmill gitsync-settings, etc.)
accept --workspace and apply the matching workspace's overrides / specificItems.
Binding a workspace to wmill.yaml
wmill workspace bind interactively creates or updates a workspaces: entry from the active
local profile. It prompts for the workspace name, git branch (optional), and handles the
workspaceId / baseUrl fields for you.
wmill workspace bind # interactive: pick profile, name, branch
wmill workspace bind --workspace staging # non-interactive: use "staging" as the key
wmill workspace unbind --workspace staging # remove baseUrl/workspaceId from the entry
wmill init also runs the bind flow automatically for fresh projects.
Configuration resolution priority
Settings are resolved in this order (highest priority first):
- CLI flags (highest priority)
- Promotion overrides (if
--promotionflag is used) - Workspace overrides (from the resolved workspace's
overridesblock) - Top-level settings (base configuration)
Migrating from gitBranches / environments
Before this change, the CLI had three separate keys — gitBranches, environments, and
git_branches — with the branch name as the map key and workspaceId always required.
Unifying everything under workspaces: removes that duplication: one key, one flag
(--workspace), and sensible defaults so most entries drop to two lines.
Legacy configs keep working. On the first read of a legacy wmill.yaml, the CLI normalizes
it to workspaces: in memory and prints a one-time deprecation warning. --branch and
--env on sync commands are still accepted with a deprecation warning — prefer --workspace.
To migrate the file on disk in one command:
wmill config migrate
# ✅ Migrated 'gitBranches' to 'workspaces' in wmill.yaml
# Workspace entries: main, staging
The migration preserves every field — baseUrl, workspaceId, overrides,
promotionOverrides, specificItems, commonSpecificItems. Since legacy keys were branch
names, the resulting workspace name equals the old branch name and gitBranch is left
implicit (it defaults to the key). Workspace-specific item files
keep the same suffix — the file database.main.resource.yaml stays valid because the
workspace is still named main.
Inspecting the config
wmill config prints a structured reference of every option with its default, type, and
description. Pair with --json for machine-readable output.
wmill config # human-readable reference
wmill config --json # JSON reference, one entry per option
wmill config migrate # legacy → workspaces
Codebase configuration
Advanced bundling configuration for TypeScript/JavaScript projects:
codebases:
- relative_path: './my-lib'
includes: ['**/*.ts']
excludes: ['**/*.test.ts']
assets:
- from: 'assets/'
to: 'static/'
customBundler: 'esbuild'
external: ['lodash', 'react']
define:
NODE_ENV: 'production'
inject: ['./polyfills.js']
All sync options for your workspace can be found on this file:
export interface SyncOptions {
raw?: boolean;
yes?: boolean;
skipPull?: boolean;
failConflicts?: boolean;
plainSecrets?: boolean;
json?: boolean;
// File filtering options
skipVariables?: boolean;
skipResources?: boolean;
skipResourceTypes?: boolean;
skipSecrets?: boolean;
skipScripts?: boolean;
skipFlows?: boolean;
skipApps?: boolean;
skipFolders?: boolean;
// Include options
includeSchedules?: boolean;
includeTriggers?: boolean;
includeUsers?: boolean;
includeGroups?: boolean;
includeSettings?: boolean;
includeKey?: boolean;
skipBranchValidation?: boolean;
// Path and message options
message?: string;
includes?: string[];
extraIncludes?: string[];
excludes?: string[];
defaultTs?: 'bun' | 'deno';
codebases?: Codebase[];
// Workspace bindings — the single unified config key
workspaces?: {
commonSpecificItems?: {
variables?: string[];
resources?: string[];
triggers?: string[];
folders?: string[];
settings?: boolean;
};
[workspaceName: string]: SyncOptions & {
// Defaults to the key (workspace name) when omitted
gitBranch?: string;
workspaceId?: string;
baseUrl?: string;
overrides?: Partial<SyncOptions>;
promotionOverrides?: Partial<SyncOptions>;
specificItems?: {
variables?: string[];
resources?: string[];
triggers?: string[];
folders?: string[];
settings?: boolean;
};
};
};
// Deprecated — still read, normalized to `workspaces` in memory
gitBranches?: unknown;
environments?: unknown;
git_branches?: unknown;
promotion?: string;
}
Example repo for syncing with Windmill in git
We provide an example repo for syncing with Windmill:
Cloning an instance
Instances can be cloned with Windmill CLI. This is useful for instance migration or for setting up a new (prod) instance with the same configuration as an existing one.
Pulling an instance
To pull instance users, groups, settings and configs (=worker groups+SMTP) from the instance use wmill instance pull.
You can skip any of those using --skip-XXX.
You can also pull all workspaces at the same time by adding the --include-workspaces option. It will setup for each a local workspace (including a folder in the current directory) with a prefix specific to the instance (based on the name you've given to the instance).
Pushing an instance
To push to an instance use wmill instance push. It takes the same options as pulling an instance.
When using --include-workspaces, you will also have to select the instance prefix of the local workspaces you want to push (make sure you're in its parent folder).
During the push process, you will be prompted to decide whether to re-encrypt the secrets of the workspace on the remote instance. In the case of instance migration, it is recommended to select "no" as the secrets are already encrypted with the correct key.