# Workspace forks

> How do I create workspace forks for parallel development workflows in Windmill?

Workspace forks allow users to create independent copies of a workspace, enabling parallel development workflows similar to git branches and GitHub forks.

:::tip Forks are stage 2 of the collaboration guide

Adopting workspace forks corresponds to **stage 2** of the [collaboration and deployment stages](../23_canonical_deployment_setups/index.mdx#stage-2--add-forks-and-the-merge-ui) guide. If you are setting up collaboration for a team, that guide explains how forks fit alongside protection rulesets, git sync, and multi-workspace promotion.

:::

## How workspace forks work

Workspace forks create a complete copy of your workspace, including scripts, flows, apps, resources, variables, and [data tables](../../core_concepts/11_persistent_storage/data_tables.mdx). Each fork operates independently and changes can later be merged to a parent workspace through a couple of methods.

Key features:
- **Independent development**: Make changes without affecting the parent workspace
- **Git integration**: Automatically creates corresponding git branches using the git sync workflow
- **Team collaboration**: Multiple developers can work on separate forks simultaneously
- **Data table forking**: Data tables are included in the fork, giving each fork its own isolated copy of the data
- **DuckLake data environments**: Pipelines that materialize [DuckLake](../../core_concepts/11_persistent_storage/ducklake.mdx) tables write to a fork-scoped namespace and read not-yet-materialized tables from the parent (see [Fork data environments for DuckLake](#fork-data-environments-for-ducklake))
- **CLI merge**: Use `wmill workspace merge` to merge changes from a fork back to the parent workspace

![Workspace Forks and Git Sync](/images/workspace_forks.png)

## Example feature development workflow

1. **Create fork**: Fork the main workspace for new feature development
2. **Develop**: Make changes in the forked workspace
3. **Create PR**: Use github/gitlab to create a pull request
4. **Review & merge**: Team reviews changes before merging to main branch
5. **Sync back changes**: Changes are automatically synced to the workspace linked to the main branch

## Managing workspace forks

### Prerequisites

- On self-hosted Windmill, forks work on any edition. On Community Edition, forks count toward the global 3-workspace limit; [Enterprise Edition](/pricing) removes that cap.
- On [Windmill Cloud](https://app.windmill.dev/), forking is available for workspaces on a paid (Team or Enterprise) plan only. See [Forks on Windmill Cloud](#forks-on-windmill-cloud) below.
- [Git sync](../11_git_sync/index.mdx) configured (optional, required only for git-based workflows)
- [CI/CD actions ](../9_deploy_gh_gl/index.mdx#github-actions-setup) to merge back from the git remote to Windmill (only required for git sync workflows)

### Forks on Windmill Cloud

On [Windmill Cloud](https://app.windmill.dev/), forking is available for workspaces on a paid (Team or Enterprise) plan. Free-tier workspaces cannot create forks - upgrade the workspace to a paid plan first (see [plans & how to upgrade](../../enterprise/1_plans_details/index.mdx#upgrading-to-team-edition)).

A fork inherits its parent (root) workspace's plan and usage limits rather than getting its own:

- **Shared plan**: a fork takes on the premium status of its root workspace. You don't subscribe a fork separately.
- **Shared usage limits**: a fork's executions are metered into the root workspace's usage, so all forks in a family draw from the same pool and count toward the root's usage-based bill. Billing stays with the parent - a fork never has its own invoice.
- **Per-seat fork cap**: the number of forks a workspace family can have is capped at 5 per paid (developer) seat of the root workspace (for example, 2 seats allow up to 10 forks). Delete a fork or add seats to free up capacity.

If a workspace is later downgraded to the free tier, its existing forks keep working under the root's shared free-tier limits, but no new forks can be created until it is upgraded again.

On self-hosted Windmill, none of these cloud limits apply - forks are governed only by your edition's workspace limit (see [prerequisites](#prerequisites) above).

### Fork creation

#### From the UI

1. Navigate to the workspace you want to fork.
2. Click on the Workspace menu on the sidebar.
3. Click on `Fork current workspace`.
4. Configure the name and id and then press on the `Fork workspace` button.

#### From the CLI

1. Enter the local repo being synced with the workspace you want to fork.
2. Run `wmill workspace fork` and follow the prompts to name your workspace. This uses your `workspaces` settings in `wmill.yaml` to determine which workspace to fork from based on the branch you are on.
3. The fork will be created on your instance and is ready to be worked on.
4. Run the command shown on your terminal to create the corresponding branch and start adding edits to your newly created fork!

<!-- TODO: Add step-by-step screenshots of the fork creation process -->

### Deleting a fork

Workspace forks can be deleted by their creator, or by any workspace admin.

#### From the UI

Just press the cogwheel on the sidebar and select the option to delete the workspace fork.

#### From the CLI

1. Find the workspace profile corresponding to the workspace fork using the `wmill workspace` command.
2. If missing, add it using `wmill workspace add`
3. Now run `wmill workspace delete-fork <profile_name>`

### Permissions and access

Forks can be created by any user of the workspace. Roles and access will be the same as in the original workspace. All users of the original workspace can join the fork at any time, which will make it appear as another workspace on their menu.

On [Windmill Cloud](https://app.windmill.dev/), creating a fork additionally requires the workspace to be on a paid plan and to have fork capacity left under the [per-seat cap](#forks-on-windmill-cloud).

## Git sync integration

When workspace forks are used with [git sync](../11_git_sync/index.mdx), Windmill will automatically create a git branch on the same repository as
the parent workspace, and keep track of changes there. External changes to the branch are then synced back to the forked workspace if the appropriate
CI/CD actions are setup. Once the changes are ready, a PR can be opened to sync back changes to the original workspace.

Workspace forks can be used with git sync for full version control integration. If using git sync, make sure to have it properly setup and in particular the [CI/CD actions](../9_deploy_gh_gl/index.mdx#github-actions-setup) specific to forks should be set. Make sure to not forget the `push-on-merge-to-forks.yaml` action.

### Fork and branch naming

Forks workspace IDs are always prefixed by `wm-fork-`. The associated branch name is always prefixed by `wm-fork/<originalBranch>/` where `<originalBranch>` is the branch that is associated with the original branch was worked from.

For example:

| Workspace ID | Default or selected branch of repo associated with workspace | Fork Workspace ID | Branch associated to fork |
|--------------|--------------------------------------------------------------|-------------------|---------------------------|
| windmill     | main                                                         | wm-fork-feature1  | wm-fork/main/feature1     |

This naming convention is what enables Windmill to match branches to workspaces without having to reconfigure it for each branch. This is why fork workspace ids and branch names cannot be changed.

## Synchronization and merging

### Simple edits with the deployment UI

Once a fork is created, it is automatically setup to deploy to its parent branch. You can deploy any item to the parent branch directly from the UI by clicking it and selecting `Deploy to staging/prod`. Learn more about the [Deployment UI](../../core_concepts/12_staging_prod/index.md).

### Merge workspaces from the UI (Merge UI)

You can merge changes from a forked workspace back to its parent workspace directly from the UI without requiring git sync. This provides a simpler workflow for teams that don't need full git integration.

To merge a fork:

1. Navigate to the forked workspace
2. On the home page, a banner will show you the diff with its parent workspace. Click `Review & Deploy Changes`
3. Review the changes and confirm the merge

#### Items behind and conflicts

If it is detected that some of the differences come in fact from the original workspace, these changes will be shown as 'behind'. Before deploying, all behind changes must be updated into the fork and properly tested. When an items has changes that are both ahead and behind, it will be marked as a conflict and will be warned to the user. The user must then review the diff and decide how to edit the item to resolve the conflicting changes. Until deployed or updated however, the item will still be marked as a conflict. For more granular conflict detection and better tracked resolution, you can choose to merge the workspaces through a git workflow instead.

The merge will transfer all selected scripts, flows, apps, resources, and variables from the fork to the parent workspace.

#### Triggers and schedules

Workspace forks do not copy over triggers when being created. This avoids unnecessary executions or confusing collisions. However, you can still create triggers on your fork to test something, and the merge UI will remind you which ones have been created so that you can manually deploy them and/or disable them or delete them.

### Git sync based merging

When git sync is properly setup, the workspace is synchronized with one of the branches in your repo, and the forks are also synchronized with their own branches. It is then possible your preferred git workflow to merge into or out of these branches to sync, so you can update the fork, or deploy changes into the parent workspace.

## Fork data environments for DuckLake

When the parent workspace has [DuckLake](../../core_concepts/11_persistent_storage/ducklake.mdx) lakes configured, a fork gets its own isolated data environment for each lake by default. Running a [pipeline](../../core_concepts/63_pipelines/index.mdx) that [materializes](../../core_concepts/63_pipelines/materialization.mdx) DuckLake tables in a fork writes to a fork-scoped namespace (a separate metadata schema in the same catalog database, and a dedicated `__wm_forks/<fork id>/...` prefix in the bucket) instead of the parent workspace's tables. This applies transparently: scripts keep the same `ducklake://<lake>/<table>` URIs and annotations, and the redirect covers managed materialization, raw SQL, previews and backfills alike.

This also applies to [dev workspaces](../26_dev_workspaces/index.mdx), which are persistent forks.

### Reading parent data (defer)

For every table the parent workspace has materialized but the fork has not, the fork sees a read-only view over the parent's current data, in the style of dbt's defer. You can iterate on a single pipeline step in a fork without first rebuilding everything upstream: consumers read the parent's data until the fork materializes the table itself, at which point they switch to the fork's own copy. For an [SCD2 history](../../core_concepts/63_pipelines/materialization.mdx#scd2-history-slowly-changing-dimensions) table, the `<table>_current` companion view is deferred as well.

Parent lakes are attached read-only in a fork, so a fork job cannot write the parent's data through DuckLake even accidentally.

### Isolated vs shared with parent

When the parent workspace has lakes configured, the fork-creation dialog shows a `Ducklake data environment` section with a per-lake choice:

- **Isolated** (default): the fork gets the isolated data environment described above.
- **Shared with parent**: the fork reads and writes the parent's lake directly, with no isolation. Useful for forks that must operate on production data, such as a prod-equivalent backfill; a warning is shown since pipeline runs in the fork will modify the parent's tables.

The choice is stamped per lake on the fork and is visible in the fork's workspace settings under `Object storage (S3)` -> Ducklake, where each lake shows an isolated or shared-with-parent chip. Forks created through the API without an explicit choice default to isolated.

Lakes using a MySQL catalog cannot be isolated (the fork-scoped metadata schema requires a PostgreSQL catalog): pipeline runs against such a lake fail with an explicit error in an isolated fork, while sharing the lake with the parent works.

{/* TODO: screenshot of the fork-creation dialog showing the "Ducklake data environment" section with per-lake Isolated / Shared with parent choice and the warning callout on shared. */}

### Fork status in the pipeline graph

In a fork, each DuckLake asset in the [pipeline graph](../../core_concepts/63_pipelines/index.mdx#the-pipeline-graph) carries a chip showing where its data currently comes from:

- an amber `deferred` chip: the fork has not materialized this table yet, reads are deferred to the parent's data;
- an emerald `fork` chip: the fork has materialized its own copy in its isolated namespace.

Selecting the asset shows a matching banner in the details pane.

{/* TODO: screenshot of a fork's pipeline graph with amber "deferred" and emerald "fork" chips on asset nodes, and the details-pane banner. */}

### Cleanup

Deleting a fork also offers to drop its forked DuckLake namespaces: the fork-scoped metadata schema and the `__wm_forks/<fork id>/...` files in the bucket. The same cleanup is available as an API endpoint, `POST /w/<workspace>/workspaces/drop_forked_ducklake_namespaces`, which requires the same permission as deleting the workspace. It is safe to retry if a previous attempt failed partway. Lakes shared with the parent have nothing to clean up.

## Dev workspaces

A dev workspace is a persistent fork that serves as a team's standing editable environment, paired with a "prod" workspace that can be locked so changes only reach it through promotion. Unlike an ordinary fork, a dev workspace keeps a plain, git-branch-safe workspace ID (no `wm-fork-` prefix), and there is at most one per workspace. Use a dev workspace when you want a long-lived dev/prod pair rather than the create-merge-delete lifecycle of an ordinary fork.

## Related features

### Workspace-specific tags and forks

When [workspace-specific default tags](../../core_concepts/9_worker_groups/index.mdx#set-tags-to-assign-specific-queues) are enabled (the instance setting that appends the workspace id to every job tag, e.g. `python3-myworkspace`), forked workspaces automatically resolve to their **parent workspace's tag**. This means fork jobs are routed to the same worker group as the parent workspace — there is no need to provision separate workers for every fork.

For example, if the parent workspace is `staging` and a fork `wm-fork-feature1` runs a Python script, the job tag will be `python3-staging` (not `python3-wm-fork-feature1`).

#### Dedicated fork workers (optional)

If you want to route fork jobs to their own worker pool — for instance to isolate fork workloads from the parent — you can enable the **"Fork workspace tag: append -fork suffix"** toggle in the instance settings under the **Default Tags** section. When enabled, fork jobs are tagged with the parent workspace id plus a `-fork` suffix (e.g. `python3-staging-fork`), so all forks of a given parent share a common dedicated tag. You can then provision a worker group listening to `*-staging-fork` tags to handle all fork jobs for that parent.

### OIDC tokens in forks

Job [OIDC](../../core_concepts/29_oidc/index.mdx) tokens generated in a fork carry the fork's own workspace id in the `workspace` claim, plus a `fork_parent_workspace` claim containing the parent workspace id. External OIDC consumers such as Vault can bind policies to `fork_parent_workspace` so a single policy covers a workspace and all its forks. See [Token payload reference](../../core_concepts/29_oidc/index.mdx#token-payload-reference).
