# Sync

> How do I synchronize folders and git repositories to a Windmill instance using the CLI?

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](./environment-specific-items.mdx) 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](../../cli_local_dev/1_vscode-extension/index.mdx).

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: false
skipResources: false
skipResourceTypes: false
skipSecrets: true
includeSchedules: false
includeTriggers: false
```

See more details in [wmill.yaml](#wmillyaml).

Note that here, secrets are skipped by default (for security) and schedules and triggers are not included by default, while variables, resources and resource types are synced. 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.

```bash
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](../11_git_sync/index.mdx).

### Pulling

`wmill sync pull` will simply pull all files from the currently
[selected workspace](./workspace-management.md#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](./workspace-management.md#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.

```bash
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.

```bash
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](https://github.com/windmill-labs/windmill-sync-example/blob/main/wmill.yaml).

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

For a human-readable walkthrough of every `wmill.yaml` option — with defaults, effects, and deprecated keys — see the [`wmill.yaml` reference](./wmill-yaml-reference.mdx).

```bash
wmill config            # human-readable reference
wmill config --json     # JSON reference, one entry per option
wmill config migrate    # legacy → workspaces
```

A JSON Schema is also committed at [`cli/wmill.schema.json`](https://github.com/windmill-labs/windmill/blob/main/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.

```yaml
# 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

```yaml
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.

```yaml
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.
- **`workspaceId` defaults to the key** — only set it when the Windmill workspace id differs from the name.
- **`gitBranch` defaults to the key** — only set it when the git branch name differs.
- **`baseUrl`** points at the Windmill instance; the CLI resolves it to a local
  [profile](./workspace-management.md) (baseUrl + workspaceId match), so the same
  `wmill.yaml` works on any machine as long as a matching profile is logged in.
- **`overrides`** override any top-level sync option when this workspace is the current one.
- **`promotionOverrides`** apply when pushing to this workspace with `--promotion`.
- **`specificItems`** declare items that are stored per-workspace on disk (see
  [workspace-specific items](./environment-specific-items.mdx)).

### Selecting a workspace

The CLI picks the active workspace in this order:

1. `--workspace <name>` — explicit override, matches a key in `workspaces:` (or a local profile name as a fallback).
2. **Current git branch** — the first entry whose effective `gitBranch` matches `git rev-parse --abbrev-ref HEAD`.
3. **Local profile** — the active profile set via `wmill workspace switch` (used when no `workspaces:` 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.

```bash
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):

1. **CLI flags** (highest priority)
2. **Promotion overrides** (if `--promotion` flag is used)
3. **Workspace overrides** (from the resolved workspace's `overrides` block)
4. **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:

```bash
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](./environment-specific-items.mdx)
keep the same suffix — the file `database.main.resource.yaml` stays valid because the
workspace is still named `main`.

See [Generating and inspecting the config](#generating-and-inspecting-the-config) for the `wmill config` reference commands.

### Codebase configuration

Advanced bundling configuration for TypeScript/JavaScript projects:

```yaml
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](https://github.com/windmill-labs/windmill/blob/main/cli/src/core/conf.ts):

```ts

  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](./workspace-management.md#managing-encryption-keys) 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.

### Instance-level Slack

The CLI also supports connecting Slack at the instance level (global bot token used for critical alerts) non-interactively:

```bash
wmill instance connect-slack \
  --bot-token <xoxb-…> \
  --team-id <T…> \
  --team-name "<team name>"
```

Super-admin only. Writes the `global_settings.slack` row and the encrypted variable + resource at `f/slack_bot/global_bot_token` in the `admins` workspace — same artifacts the UI's "Connect instance to Slack" button produces. See the [Slack integration doc](../../integrations/slack.mdx#action-on-windmill-from-slack) for the full flow, including configuring the OAuth client (`client_id`/`client_secret`) via `wmill instance push`.
