# Workspace-specific items

> How do I configure per-workspace resources and variables in Windmill? Store different versions of resources, variables, triggers, folders, and settings per workspace on disk.

Workspace-specific items let you store different versions of
[resources](../../core_concepts/3_resources_and_types/index.mdx),
[variables](../../core_concepts/2_variables_and_secrets/index.mdx), triggers, folders, and
workspace settings per workspace (dev / staging / prod). The CLI transforms file paths so
that each workspace has its own namespaced files locally while keeping clean base paths in
the Windmill workspace itself.

:::tip Multi-workspace setups are stage 4 of the collaboration guide

Using `workspaces:` with multiple entries (one per environment, each with its own
`gitBranch`) corresponds to **stage 4** of the
[collaboration and deployment stages](../23_canonical_deployment_setups/index.mdx#stage-4--add-multi-workspace-promotion)
guide. The single-workspace form of `workspaces:` is used starting at
[stage 3](../23_canonical_deployment_setups/index.mdx#stage-3--add-git-sync).

:::

This replaces the older split between `gitBranches` (multi-branch) and `environments`
(single-branch). Both are now a single `workspaces:` key — see
[Migrating from `gitBranches` / `environments`](./sync.mdx#migrating-from-gitbranches--environments).

## How it works

When a file matches a pattern in your `wmill.yaml` configuration, the CLI inserts the
workspace name into the local file path:

- **Local files**: `database.dev.resource.yaml`, `database.prod.resource.yaml`
- **Windmill workspace**: `database.resource.yaml` (clean base path)

Each workspace has its own version of the same logical resource. In practice, multiple
workspace-specific files usually coexist in the repository (e.g. `database.dev.resource.yaml`
and `database.prod.resource.yaml` side by side) — by design when one git branch hosts
multiple workspaces, or as a result of merging branches when each workspace has its own
branch. The CLI only reads the files matching the current workspace and ignores the others.

## Configuration

Workspace-specific items are declared inside a `workspaces:` entry, either per workspace
under `specificItems`, or shared across every workspace under `commonSpecificItems`.

```yaml
workspaces:
  dev:
    baseUrl: https://dev.windmill.dev
    overrides:
      skipSecrets: true
    specificItems:
      resources:
        - "u/alex/config/**"
      variables:
        - "u/alex/env_*"
      triggers:
        - "u/alex/kafka_*"
      folders:
        - "f/env_*"
      settings: true

  prod:
    baseUrl: https://app.windmill.dev
    gitBranch: main              # workspace "prod" lives on git branch "main"
    overrides:
      skipSecrets: false
    specificItems:
      resources:
        - "u/alex/config/**"
      variables:
        - "u/alex/env_*"
      folders:
        - "f/env_*"
      settings: true

  # Items that are workspace-specific across ALL workspaces
  commonSpecificItems:
    resources:
      - "u/alex/config/**"
    variables:
      - "u/alex/database_*"
    folders:
      - "f/env_*"
    settings: true
```

The `workspaces` key is the map key (`dev`, `prod`, …) and is also the suffix inserted into
file names on disk. `gitBranch` and `workspaceId` default to that key — only set them when
they differ.

## Resolving the current workspace

`wmill sync pull` and `wmill sync push` pick the workspace in this order:

1. `--workspace <name>` — explicit override, looks up the entry by key.
2. **Current git branch** — the first entry whose effective `gitBranch` matches
   `git rev-parse --abbrev-ref HEAD`.
3. No match — top-level sync options are used as-is, no workspace-specific transform.

### One git branch per workspace

Each workspace has its own git branch. The CLI detects the current branch automatically and
applies the matching entry.

```bash
git checkout main
wmill sync pull
# Uses workspace "prod" (gitBranch: main)
# Creates: u/alex/config/database.prod.resource.yaml

git checkout dev
wmill sync pull
# Uses workspace "dev"
# Creates: u/alex/config/database.dev.resource.yaml
```

### Multiple workspaces on one git branch

All workspaces live on the same branch and you pick the target with `--workspace`.

```bash
wmill sync pull --workspace dev
# Creates: u/alex/config/database.dev.resource.yaml

wmill sync pull --workspace prod
# Creates: u/alex/config/database.prod.resource.yaml

wmill sync push --workspace staging
```

No `git checkout` is needed — all workspace-specific files coexist on the same branch.

:::info Migrating from `--env` and `--branch`
`--branch` and `--env` are still accepted on sync commands but are deprecated — they print a
one-time warning and resolve the same way `--workspace` does. Update any scripts or CI jobs
to use `--workspace` instead.
:::

## File path transformation

### Transform logic

**Pull** (Windmill workspace → local):
- `u/alex/database.resource.yaml` → `u/alex/database.prod.resource.yaml`
- `u/alex/orders.kafka_trigger.yaml` → `u/alex/orders.prod.kafka_trigger.yaml`
- `f/env_staging/folder.meta.yaml` → `f/env_staging/folder.prod.meta.yaml`
- `settings.yaml` → `settings.prod.yaml`

**Push** (local → Windmill workspace):
- `u/alex/database.dev.resource.yaml` → `u/alex/database.resource.yaml`
- `u/alex/orders.dev.kafka_trigger.yaml` → `u/alex/orders.kafka_trigger.yaml`
- `f/env_staging/folder.dev.meta.yaml` → `f/env_staging/folder.meta.yaml`
- `settings.dev.yaml` → `settings.yaml`

The suffix is the **workspace name** (the key in `workspaces:`). In migrated configs where
the workspace name equals the old branch name, existing files keep working unchanged.

### Resource files

Resources can include associated files (certificates, config files, etc.) alongside their
YAML definitions. These follow the same transformation:

- Base file: `u/alex/certificate.resource.file.pem`
- Workspace-specific: `u/alex/certificate.dev.resource.file.pem`

When a resource YAML file is marked as workspace-specific, all associated resource files are
automatically treated the same way.

### Supported file types

- **Variables**: `*.variable.yaml`
- **Resources**: `*.resource.yaml` and resource files (`*.resource.file.*`)
- **Triggers**: `*.kafka_trigger.yaml`, `*.http_trigger.yaml`, `*.websocket_trigger.yaml`, `*.nats_trigger.yaml`, `*.postgres_trigger.yaml`, `*.mqtt_trigger.yaml`, `*.sqs_trigger.yaml`, `*.gcp_trigger.yaml`
- **Schedules**: `*.schedule.yaml`
- **Folders**: `folder.meta.yaml` files within folder directories
- **Settings**: `settings.yaml` (workspace settings file)

## Pattern matching

Patterns support standard glob syntax:

- `*` matches any characters within a path segment
- `**` matches any characters across path segments
- `u/alex/database_*` matches `u/alex/database_config`, `u/alex/database_url`, etc.
- `f/environments/**` matches all files under `f/environments/` recursively

## Common specific items

Items that should be workspace-specific across all workspaces can be declared once under
`commonSpecificItems`:

```yaml
workspaces:
  commonSpecificItems:
    variables:
      - "u/alex/database_*"
      - "f/config/**"
    resources:
      - "u/alex/api_keys/**"
      - "f/environments/**"
    triggers:
      - "u/alex/kafka_*"
      - "f/streaming/**"
    folders:
      - "f/env_*"
    settings: true
```

## Per-workspace specific items

If some resources only exist in certain workspaces, define different patterns per workspace
instead of using `commonSpecificItems`:

```yaml
workspaces:
  prod:
    specificItems:
      variables:
        - "u/alex/prod_*"
      resources:
        - "u/alex/production/**"
      triggers:
        - "u/alex/prod_kafka_*"
      folders:
        - "f/prod_config"
      settings: true

  dev:
    specificItems:
      variables:
        - "u/alex/dev_*"
      resources:
        - "u/alex/development/**"
      triggers:
        - "u/alex/dev_kafka_*"
      folders:
        - "f/dev_config"
      settings: true
```

Here, `prod_kafka_*` triggers only get the workspace suffix on `prod` — they don't exist in
`dev`, so there's no need to mark them there. Most setups use `commonSpecificItems` since
the same resources typically exist across all workspaces.

## Name safety

Workspace names containing certain characters are automatically sanitized for filesystem safety:

- **Unsafe characters**: `/ \ : * ? " < > | .`
- **Replacement**: All unsafe characters are replaced with `_`
- **Warning**: The CLI shows a clear warning when sanitization occurs

```bash
Warning: Branch name "feature/api-v2.1" contains filesystem-unsafe characters (/ \ : * ? " < > | .)
and was sanitized to "feature_api-v2_1". This may cause collisions with other similarly named branches.
```

Prefer simple workspace keys (`dev`, `staging`, `prod`) to avoid collisions.

## Best practices

- **Start broad**: Use `commonSpecificItems` for widely-used resources
- **Be specific**: Target exact paths rather than overly broad patterns
- **Group logically**: Organize patterns by team, workspace, or function
- **Define patterns early**: Establish patterns before team members start using them
- **Test locally**: Verify workspace-specific behavior works correctly before CI/CD integration

## Troubleshooting

### Files not being detected as workspace-specific

1. **Check patterns**: Ensure your glob patterns match the file paths exactly
2. **Verify file types**: Only the types listed above are supported
3. **Pattern testing**: Use tools like [globster.xyz](https://globster.xyz/) to test your patterns

### Files syncing to the wrong workspace

1. **Check config**: Ensure your `workspaces:` entry is correct
2. **Verify current context**: Make sure you're on the expected git branch or using the right `--workspace` flag
3. **Configuration precedence**: CLI flags override configuration file settings

## Git sync integration

When [Git sync](../11_git_sync/index.mdx) is configured, workspace-specific items work
seamlessly with your Git repository setup. On each Windmill-triggered sync, the backend
invokes the CLI with `--base-url` and `--workspace` set to the current instance and
workspace id. The CLI matches these against the `workspaces:` entries in `wmill.yaml`
(on `baseUrl` + `workspaceId`) and applies the corresponding entry — no extra configuration
on the Windmill side.

- **One git branch per workspace**: each Windmill workspace's `git_repository` resource
  points to a different branch, and the `wmill.yaml` entry for that workspace sets
  `gitBranch` accordingly.
  - **Production workspace**: `git_repository` pointing to `repoX` on `main` branch
    - Changes to `database.resource.yaml` → pushed as `database.prod.resource.yaml`
  - **Development workspace**: `git_repository` pointing to `repoX` on `dev` branch
    - Changes to `database.resource.yaml` → pushed as `database.dev.resource.yaml`

- **Multiple workspaces on one git branch** (mono-branch): all Windmill workspaces point to
  the same branch. List each workspace under `workspaces:` with a distinct `workspaceId`
  (and the same `gitBranch`); the CLI selects the right entry from the `--workspace` flag
  the backend passes, so no UI toggle is needed.

## Related documentation

- [CLI sync](./sync.mdx) - Main sync operations and configuration
- [Git sync settings](./gitsync-settings.mdx) - Managing sync configuration
- [Git sync](../11_git_sync/index.mdx) - Backend Git integration
