# Windmill > Windmill is an open-source and self-hostable workflow engine and developer platform to build, orchestrate, and monitor internal software at scale. Build workflows, internal tools, AI agents and automations. Full flexibility of code with 20+ languages, public and private library imports, and a local development experience optimized for AI (Claude Code, Cursor, CLI & VS Code extension). Enterprise-grade integrations with Postgres, Snowflake, Kafka, DuckDB and 50+ services. Enterprise-grade security with SSO, SAML, SCIM and audit logs. Built in Rust, optimized for scale. Website: https://www.windmill.dev Documentation: https://www.windmill.dev/docs GitHub: https://github.com/windmill-labs/windmill Cloud: https://app.windmill.dev Hub: https://hub.windmill.dev OpenAPI: https://app.windmill.dev/openapi.html This file contains the entire Windmill documentation as a single document. A curated per-page index with one-line descriptions is available at https://www.windmill.dev/llms.txt. ## Browser automation Source: https://www.windmill.dev/docs/advanced/browser_automation # Browser automation Windmill makes it easy to perform browser automation tasks, such as testing or web scraping. :::info Not sure what a worker group is? You should probably [read about it first](../../core_concepts/9_worker_groups/index.mdx). ::: By default, a worker group named `reports` is available which will handle jobs with the `chromium` tag. Workers assigned to this group will install chromium on start (learn more about [init scripts](../../core_concepts/9_worker_groups/index.mdx#init-scripts)). You have to set the worker group of at least one worker to `reports`. There is a sample worker container definition called `windmill_worker_reports` in the `docker-compose.yml` file which you can uncomment to quickly start a worker with the right worker group. The chromium binary will be available on these workers at `/usr/bin/chromium`. You will need to disable the sandbox to run it inside windmill workers. You can do this by passing the `--no-sandbox` flag. :::caution Running chromium without the sandbox is a security risk. Make sure you trust the website you are visiting. ::: To run jobs on a chromium-equipped worker, you have to select the `chromium` tag in the settings of the script or flow step. [Learn how here](../../core_concepts/9_worker_groups/index.mdx). ## Examples ### Playwright (Bun) ```typescript const page = await browser.newPage(); await page.goto("https://google.com"); const title = await page.title(); await browser.close() return title } ``` ### Puppeteer (Bun) ```typescript const page = await browser.newPage(); await page.goto("https://google.com"); const title = await page.title(); await browser.close(); return title; } ``` --- ## Canonical deployment setups Source: https://www.windmill.dev/docs/advanced/canonical_deployment_setups # Collaboration and deployment stages This page describes how to set up collaboration and deployment in Windmill as a progression of four stages, from simplest to most advanced. Each stage is additive: it builds on the previous one and adds one capability. Most teams settle at stage 2 or stage 3 — stage 4 is only needed when you need a separate production environment with a promotion workflow. | Stage | What you add | Git required | Forks | Workspaces | |-------|--------------|--------------|-------|------------| | [Stage 1](#stage-1--shared-workspace) | Shared workspace, `u/` → `f/` | No | No | 1 | | [Stage 2](#stage-2--add-forks-and-the-merge-ui) | Forks, merge UI, protection rulesets | No | Yes | 1 | | [Stage 3](#stage-3--add-git-sync) | Bi-directional git sync, local development | Yes | Yes | 1 | | [Stage 4](#stage-4--add-multi-workspace-promotion) | Multi-workspace (dev/staging/prod) | Yes | Yes | 2+ | :::info Edition requirements - **Stage 1** works on any edition. - **Stage 2** relies on [workspace forks](../20_workspace_forks/index.mdx) and [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx). Forks are available on all self-hosted editions but count toward the global 3-workspace limit on Community Edition. On [Windmill Cloud](https://app.windmill.dev/), forks require a [paid plan](../20_workspace_forks/index.mdx#forks-on-windmill-cloud). - **Stage 3** adds [git sync](../11_git_sync/index.mdx), available on [Cloud and Enterprise Self-Hosted](/pricing), and on Community Edition for workspaces with **up to 2 users**. - **Stage 4** requires git sync and is [Cloud or Enterprise Self-Hosted](/pricing) only. ::: ## Stage 1 — Shared workspace Everyone on the team works in the same workspace. Developers iterate in their own [user space](../../core_concepts/16_roles_and_permissions/index.mdx#path) (`u//`) and, once an item is ready to be shared, move it to a shared [folder](../../core_concepts/8_groups_and_folders/index.mdx) (`f//`). This is the simplest possible setup and the right starting point for almost every team. ### Architecture - One workspace (for example `main`) - Developers own `u//` and iterate there - Shared items live under `f//`, with folder-level [permissions](../../core_concepts/16_roles_and_permissions/index.mdx) granting write access to the relevant group - No git, no forks, no protection rulesets ### Setup 1. Create a workspace and invite your team. 2. Create one or more [folders](../../core_concepts/8_groups_and_folders/index.mdx#folders) that represent your projects, and assign write permission to the groups that should own each project. 3. That's it — developers can start building. ### Developer workflow 1. A developer creates a script, flow or app under `u//`. Items in user space are private to them by default. 2. They iterate freely, test, and share drafts with teammates via the [draft system](../../core_concepts/0_draft_and_deploy/index.mdx). 3. When the item is ready, they move it from `u//` to the appropriate `f//`. Moving an item into a folder transfers ownership to that folder's permissions. 4. Other members of the folder's group can now edit, run, and depend on the item. ### When to move to stage 2 - Someone broke an item in the shared workspace and you wish it had been isolated. - You want a review gate before changes reach the shared workspace. - Developers want sandboxes to test risky changes without affecting anyone else. ## Stage 2 — Add forks and the merge UI Stop editing the shared workspace directly. Instead, developers create a [workspace fork](../20_workspace_forks/index.mdx) for each change, iterate in the fork, and merge back via the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui). [Protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) enforce this: direct deploys to the shared workspace are blocked except for a small `wm_deployer` group. This is still a pure UI workflow — no git, no CI/CD, no external tools. Stage 3 will add git sync on top, and forks integrate seamlessly with it when you get there. :::note Forks are optional Forks are not a prerequisite for stage 3. Git sync works on a plain shared workspace, so if you already want git history and local development, you can skip this stage and go straight to [stage 3](#stage-3--add-git-sync). Adopt forks later if you need isolated sandboxes and a review gate. ::: ### Architecture - One shared workspace (same as stage 1), now called `prod` or `main` - [Protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) block direct deploys; a `wm_deployer` group has bypass permissions - Developers create [workspace forks](../20_workspace_forks/index.mdx) to develop features - Changes merge back through the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui) ![UI only forks](./workspace_forks_ui_only.png) ### Setup #### 1. Create the `wm_deployer` group 1. Open the workspace settings and go to **Groups**. 2. Create a group called `wm_deployer` and add the users who are allowed to deploy directly or approve merges. #### 2. Enable protection rulesets 1. Go to **Workspace settings** → **Protection Rulesets**. 2. Add a rule that enables **Disable direct deployment**. 3. Add the `wm_deployer` group to the bypass list. This blocks everyone except the deployer group from modifying `f/` items directly. Developers must now use forks. ### Developer workflow 1. A developer creates a [workspace fork](../20_workspace_forks/index.mdx#fork-creation) from the shared workspace via the workspace menu. 2. They iterate, test, and deploy freely *inside* the fork — triggers are not copied over to avoid unwanted executions. 3. When ready, they open the fork home page, click **Review & Deploy Changes**, and review the diff in the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui). 4. A member of `wm_deployer` approves the merge. The changes land in the shared workspace. ### When to move to stage 3 - You want a history of changes outside Windmill (audit, backup, rollback). - You want to edit scripts and flows locally in your IDE or with [agentic coding tools](../../misc/9_guides/local_dev_with_ai/index.mdx) like Claude Code. - You want fork branches mirrored in git so PRs can be reviewed there too. - You're planning to add a separate production environment (stage 4), which requires git sync. ## Stage 3 — Add git sync Connect the workspace from stages 1 and 2 to a git repository using [git sync](../11_git_sync/index.mdx). Every deploy is committed to git, and changes pushed to the repository are synced back to Windmill via CI/CD. This gives you a full audit trail, external version control, and [local development](../4_local_development/index.mdx) — and each fork from stage 2 now automatically gets its own git branch, so the merge UI and git PRs stay in lockstep. Git sync is available on [Cloud and Enterprise Self-Hosted](/pricing), and on Community Edition for workspaces with up to 2 users. ### Architecture - The shared workspace and its forks from stage 2, unchanged - A git repository with a single branch (for example `main`) - [Git sync](../11_git_sync/index.mdx) configured on the workspace: each deploy pushes a commit - A CI/CD workflow that syncs commits back to the workspace, enabling bi-directional sync - A `wmill.yaml` file in the repository with a single entry in the [`workspaces:`](../3_cli/environment-specific-items.mdx) key - Fork branches auto-created as `wm-fork//` ![Workspace Forks + git sync](./workspace_forks.png) ### Setup #### 1. Create the git repository Create an empty repository on GitHub, GitLab or similar with a single `main` branch. #### 2. Configure git sync on the workspace 1. Go to **Workspace settings** → **Git Sync**. 2. Click **+ Add connection** and create a [`git_repository`](../../integrations/git_repository.mdx) resource pointing at your repository and the `main` branch. 3. Save. Use **Initialize Git repository** to populate the repository with the current workspace contents and a default `wmill.yaml`. See the full walkthrough in [git sync setup](../11_git_sync/index.mdx#setup---git-sync-from-windmill). #### 3. Add the CI/CD workflows Git sync only pushes *from* Windmill *to* git by default. To make changes in git flow back into the workspace, add GitHub Actions workflows (or equivalent). A working reference is the [windmill-sync-example](https://github.com/windmill-labs/windmill-sync-example) repository. You will want: - [`push-on-merge.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) — runs `wmill sync push` when commits land on `main`. - [`push-on-merge-to-forks.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) and [`open-pr-on-fork-commit.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) — keep fork branches in sync and open PRs automatically. Without the fork workflows, forks still work exactly as in stage 2 — their git branches just won't receive external edits. You will need: - A [Windmill user token](../../core_concepts/4_webhooks/index.mdx#user-token) saved as a `WMILL_TOKEN` GitHub secret. - The `WMILL_WORKSPACE` and `WMILL_URL` variables set in the workflows. Full details in [git sync CI/CD setup](../11_git_sync/index.mdx#setup---cicd-from-git-repository). #### 4. Minimal `wmill.yaml` A single-workspace `wmill.yaml` looks like this: ```yaml defaultTs: bun includes: - f/** excludes: [] skipVariables: true skipResources: true skipSecrets: true workspaces: main: baseUrl: https://app.windmill.dev gitBranch: main ``` See [workspace-specific items](../3_cli/environment-specific-items.mdx) for the full schema. ### Developer workflow 1. Developers continue to use forks from stage 2. When they create a fork, a matching `wm-fork/main/` git branch is created automatically. 2. They can now also edit the fork locally: [work on the workspace outside of Windmill](../4_local_development/index.mdx) with `wmill sync pull`, edit in their IDE, the [VS Code extension](../../cli_local_dev/1_vscode-extension/index.mdx), or an [agentic coding tool](../../misc/9_guides/local_dev_with_ai/index.mdx) like Claude Code, then `wmill sync push` back. 3. Every deploy creates a commit in the git repository. 4. Merging a fork via the merge UI *or* via a normal git PR both work — pick whichever your team prefers for review. 5. External commits (from local development or PR merges) are replayed into the workspace by the CI/CD workflow. ### When to move to stage 4 - You need a separate production environment that is fully isolated from day-to-day development. - You want PR-based review on the *promotion* step, not just the merge-to-shared step. - You're deploying across multiple Windmill instances and want promotion to go through git. To deploy item by item from the UI instead, see [deploying to a workspace on another instance](../../core_concepts/12_staging_prod/index.md#deploy-to-a-workspace-on-another-instance). ## Stage 4 — Add multi-workspace promotion Introduce one or more additional workspaces (for example `staging` and `prod`), each connected to its own git branch. Day-to-day development happens on `staging` using forks (exactly as in stage 3). When changes are ready for production, they are promoted from `staging` to `prod` through [git promotion](../9_deploy_gh_gl/index.mdx), which opens a PR on the `prod` branch. The `prod` workspace is never edited directly. Its only source of change is a merged PR on the `prod` branch. ### Architecture - Two (or more) workspaces, each on its own git branch - Each workspace has its own `git_repository` resource and its own entry under `workspaces:` in `wmill.yaml` - [Git Promotion](../11_git_sync/index.mdx#git-promotion-workflow-cross-instance-deployment-using-a-git-workflow) is configured from `staging` to `prod` - Developers edit `staging` via [forks](../20_workspace_forks/index.mdx) (stage 2 pattern) - `prod` is protected by [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) and only the promotion CI/CD can deploy to it | Workspace | Git branch | Promotes to | |-----------|------------|-------------| | staging | staging | prod | | prod | prod | — | ![Git Promotion](./git_promotion.png 'Edits to staging use the stage 2 fork workflow') ### Setup #### 1. Create the branches and workspaces 1. In your git repository, create a `staging` branch and a `prod` branch. 2. Create a `staging` workspace and a `prod` workspace in Windmill. #### 2. Configure git sync on each workspace For each workspace, go to **Workspace settings** → **Git Sync** and create a `git_repository` resource pointing at the corresponding branch: - `staging` workspace → `git_repository` on `staging` branch - `prod` workspace → `git_repository` on `prod` branch #### 3. Configure the promotion target On the `staging` workspace: 1. Under the git sync connection, click **Add promotion target**. 2. Create a **separate** `git_repository` resource targeting the `prod` branch (this is not the same resource as the one used for git sync). 3. Save. :::caution Two resources are required The git sync resource points at the workspace's *own* branch (`staging`). The promotion target points at the *target* branch (`prod`). They must be different `git_repository` resources, even if they point at the same repository. ::: #### 4. Add the CI/CD workflows - `push-on-merge.yaml` — one copy per workspace (`WMILL_WORKSPACE=staging`, `WMILL_WORKSPACE=prod`), triggered by merges to `staging` and `prod` respectively. - `open-pr-on-promotion-commit.yaml` — opens a PR on `prod` when staging promotes a change. - `push-on-merge-to-forks.yaml` and `open-pr-on-fork-commit.yaml` — unchanged from stage 3, now scoped to the `staging` workspace. See [git sync CI/CD setup](../11_git_sync/index.mdx#setup---cicd-from-git-repository) for the workflow files. #### 5. Multi-workspace `wmill.yaml` ```yaml defaultTs: bun includes: - f/** excludes: [] workspaces: staging: baseUrl: https://app.windmill.dev gitBranch: staging specificItems: resources: - "f/config/**" settings: true prod: baseUrl: https://app.windmill.dev gitBranch: prod specificItems: resources: - "f/config/**" settings: true ``` Each workspace declares its own `gitBranch`. Items listed under `specificItems` (for example, environment-specific database credentials) are stored per workspace on disk. See [workspace-specific items](../3_cli/environment-specific-items.mdx) for the full schema. :::info Per-folder ownership defaults on deploy By default, a newly deployed script, flow, app, schedule or trigger runs as whichever user pushed it. For CLI / CI/CD deploys that's the identity behind `WMILL_TOKEN` — typically a workspace admin or a dedicated deploy user that you've added to `wm_deployers`. If you want items under a folder to run as a specific service account instead — and to differ per environment — set `default_permissioned_as` rules on the folder in the target workspace: ```yaml # f/customer_x/folder.meta.yaml — committed per workspace via specificItems summary: '' display_name: customer_x owners: [] extra_perms: {} default_permissioned_as: - path_glob: 'jobs/**' permissioned_as: u/customer_x_svc # or g/customer_x or an email - path_glob: '**' permissioned_as: u/ops ``` Rules are ordered; the first `path_glob` (relative to the folder root) that matches an item wins. Applied only at **create time** and only when the caller is admin or a member of `wm_deployers` — existing items are untouched, and non-deployer users still deploy as themselves. Set `folders: ["f/**/folder.meta.yaml"]` under `specificItems` in `wmill.yaml` if you want different rules per workspace on the same path. Edit the rules in the folder settings UI, or commit them directly in `folder.meta.yaml`. The CLI also exposes per-item overrides for already-deployed items via `wmill script|flow|app|schedule|trigger set-permissioned-as `. Folder defaults are advisory: they only fill in the default when the pushed item does not already carry an explicit `on_behalf_of`. To opt in to CLI preview of which items will be affected by rules on the next push, set `syncBehavior: v1` in `wmill.yaml`. ::: #### 6. Protect `prod` Enable [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) on the `prod` workspace with no bypass group — the only way in is the promotion PR. ### Developer workflow 1. A developer needs to change something. They [fork](../20_workspace_forks/index.mdx) the **staging** workspace (not prod). 2. They iterate in the fork and merge back to `staging` via the merge UI. This is the stage 2 workflow plus the git sync integration from stage 3. 3. When the change needs to go live, they trigger a [git promotion](../9_deploy_gh_gl/index.mdx) from `staging` to `prod`. 4. A PR is opened on the `prod` branch. The team reviews it in their git platform. 5. Merging the PR triggers the `push-on-merge.yaml` workflow, which syncs `prod` to the new state. ### Advantages - Fully isolated production environment with a git PR as the only gate. - Dev/staging/prod across multiple Windmill instances if needed. - Per-workspace configuration via [workspace-specific items](../3_cli/environment-specific-items.mdx). - All the benefits of stages 1–3 still apply. ## Choosing a stage Most teams should start at **stage 1** and only move up when they feel the pain the next stage solves. Stage 4 is real operational overhead and should not be the default — the majority of teams are well served by stages 2 or 3. - **Stage 1** if you have a small team, no compliance requirements, and a single environment is fine. - **Stage 2** if you want enforced review on every change and isolated sandboxes, but don't need external version control. - **Stage 3** if you want git history, backup, local development, and the option of git PR-based review on top of stage 2. - **Stage 4** if you need a separate production environment with a promotion gate, or you deploy across multiple Windmill instances through git (the UI can also [deploy to a workspace on another instance](../../core_concepts/12_staging_prod/index.md#deploy-to-a-workspace-on-another-instance)). Stages 1 → 2 → 3 → 4 are designed to be adopted in order, but you can skip stages: a small team on Community Edition can go stage 1 → stage 3 (skip forks) to get git sync and local development without needing fork-based review. ## Related documentation --- ## Ci tests Source: https://www.windmill.dev/docs/advanced/ci_tests # CI test scripts Add a `test:` annotation at the top of any script to turn it into a CI test. When the tested [script](../../getting_started/0_scripts_quickstart/index.mdx) or [flow](../../getting_started/6_flows_quickstart/index.mdx) is deployed, the test runs automatically. ## Writing a test script ### Single target ```typescript // test: script/u/admin/my_script if (result !== 42) { throw new Error(`Expected 42, got ${JSON.stringify(result)}`); } return result; } ``` ### Multiple targets ```typescript // test: // script/u/admin/script_a // script/u/admin/script_b // flow/u/admin/my_flow export async function main() { // test all items } ``` ### Wildcards Annotations support glob patterns so a single test script can cover a whole folder tree: - `*` matches one path segment. - `**` matches any depth. ```typescript // test: script/u/admin/* // test: flow/u/team/** ``` The first pattern matches every script directly under `u/admin/`. The second matches every flow at any depth under `u/team/`. Wildcards can be combined with multi-target annotations on separate lines. ### Branching on the triggering runnable CI test jobs receive a `WM_TESTED_RUNNABLE` environment variable containing `{kind}/{path}` of the runnable that triggered the test (for example, `script/u/admin/my_script`). This lets a single test script cover multiple targets and branch on which one fired it. ```typescript // test: script/u/admin/* export async function main() { const tested = process.env.WM_TESTED_RUNNABLE!; // e.g. "script/u/admin/my_script" const path = tested.replace(/^script\//, ""); return await wmill.runScript(path, {}); } ``` ### Python ```python # test: script/u/admin/my_script import wmill def main(): result = wmill.run_script("u/admin/my_script", {}) assert result == 42 return result ``` In Python, the triggering runnable is exposed as `os.environ["WM_TESTED_RUNNABLE"]`. The annotation uses the comment syntax of each language (`//` for TypeScript, `#` for Python, etc.). The script creation page also provides ready-made CI test templates for TypeScript and Python. ## Where results appear Test scripts show a yellow **CI test** badge in the script list and detail page. On the detail page of the tested script or flow, each test is listed with its status (pass, fail, or running) and a link to the job run. Results auto-refresh while tests are running. In [workspace forks](../20_workspace_forks/index.mdx), CI results are also visible: - The fork banner shows a summary of passing, failing, and running tests across all changed items. - On the [comparison page](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui), each changed item displays a CI badge, and a test summary lists all test scripts with their latest results. ## Gate GitHub pull requests When a workspace syncs to a repository through the [GitHub App](../../integrations/git_repository.mdx#github-app) with [automatic sync from Git](../11_git_sync/index.mdx#automatic-sync-from-git) enabled, pull requests from a fork or dev workspace branch get a ["Windmill CI tests" check run](../11_git_sync/index.mdx#ci-tests-check) that runs the workspace's CI tests on the pull request head. Mark it as a required status check in GitHub to block merging until the tests pass. This is [Enterprise Edition](/pricing) only. --- ## Cli Source: https://www.windmill.dev/docs/advanced/cli # Command-line interface (CLI) The Windmill CLI, `wmill` allows you to interact with Windmill instances right from your terminal. You can also use it for various automation tasks, including [syncing](./sync.mdx) folders & [GitHub repositories](./sync.mdx), or just running all you scripts and flows. See [Installation](./installation.md) for getting started. ## Unified `list` / `get` / `new` subcommands Every item type supports the same set of subcommands so the CLI can be used as a full API client in shell scripts — piping JSON output to `jq`, etc. | Subcommand | Behavior | | ---------- | -------- | | ` list [--json]` | Lists items. With `--json`, outputs machine-readable JSON. | | ` get [--json]` | Pretty-prints an item. With `--json`, outputs the full API response. | | ` new [...]` | Bootstraps a local template file for the item. `bootstrap` remains as an alias for script/flow. | The supported types are: `script`, `flow`, `app`, `resource`, `resource-type`, `variable`, `schedule`, `folder`, and `trigger`. For `trigger`, pass `--kind ` (e.g. `http`, `websocket`, `kafka`, ...) to `new` and to `get` when multiple trigger kinds share a path. ```bash wmill script list --json | jq '.[].path' wmill trigger new f/http/webhook --kind http wmill resource get f/db/prod --json | jq .value.host ``` ## `wmill docs` The `wmill docs` command searches the Windmill documentation from your terminal. It calls the backend search endpoint and prints formatted results. ```bash wmill docs [--json] ``` ### Example ```bash wmill docs "how do I create a flow?" wmill docs "what are resources?" --json | jq .answer ``` Documentation search is an [Enterprise Edition](/pricing) feature — on a Community Edition instance, the command prints a message indicating EE is required. ## `wmill digest` `wmill digest` prints the content digest of local scripts and flows, the value job [OIDC tokens](../../core_concepts/29_oidc/index.mdx#pin-code-with-digests) carry in their `digest` and `root_digest` claims. Run it from the root of a [`wmill sync`](./sync.mdx) checkout, on script files, script metadata files or flow folders: ```bash wmill digest f/infra/agent__flow f/infra/tool.ts [--json] ``` ## Using the CLI with AI coding assistants If you drive the CLI from Claude Code, Codex, Cursor, or a similar AI coding assistant, you can give the agent up-to-date `wmill` command reference by adding the [Context7](https://context7.com) plugin and pointing it at the Windmill CLI docs index at [`context7.com/windmill-labs/windmill-cli-docs`](https://context7.com/windmill-labs/windmill-cli-docs). See [Local development with AI](../../misc/9_guides/local_dev_with_ai/index.mdx) for the full local AI workflow, including `wmill init`, the VS Code extension, and the MCP server. --- ## App Source: https://www.windmill.dev/docs/advanced/cli/app # Apps ## Listing apps The `wmill app` list command is used to list all apps in the remote workspace. ```bash wmill app ``` ## Pushing an app Pushing an app to a Windmill instance is done using the `wmill app push` command. ```bash wmill app push ``` ### Arguments | Argument | Description | | ----------- | --------------------------------- | | `file_path` | The path to the app file to push. | ### Examples 1. Push the app located at `./my_app.json`. ```bash wmill app push ./my_app.json ``` ## App access mode The access mode is the one policy field a tracked app file keeps: `app.yaml` for [low-code apps](../../apps/0_app_editor/index.mdx), `raw_app.yaml` for [full-code apps](../../full_code_apps/index.mdx). Everything else in the policy is regenerated on push. It is a tri-state: ```yaml public: true # anonymous: anyone with the URL, no login guests: true # guest: an identity required, Windmill account not # neither # publisher: workspace members with read access only ``` A pull followed by a push round-trips the mode, so an app open to [guests](../../apps/13_guest_apps/index.mdx) stays open to guests through [git sync](../11_git_sync/index.mdx). Pushing `guests: true` does not by itself let anyone in: guest access is also gated by a workspace switch and an instance switch, both read on every guest request. Opening an app to guests or to the public can additionally be restricted to workspace admins and bypass users by a [protection ruleset](../../core_concepts/56_protection_rulesets/index.mdx), which applies to the CLI as it does to the UI. ## Full-code app commands The CLI provides additional commands for [full-code apps](/docs/full_code_apps): ### Create a new full-code app ```bash wmill app new ``` Interactive wizard to scaffold a full-code app with React, Svelte or Vue. ### Start the dev server From the app directory: ```bash wmill app dev ``` Starts a local development server with hot reload and WebSocket backend. Options: `--port`, `--host`, `--entry`, `--no-open`. ### Bundle an app without deploying it ```bash wmill app bundle [app_folder] --out ``` Runs the same build `wmill app push` runs, but writes `bundle.js` and `bundle.css` to a directory instead of deploying. The folder defaults to the current directory and the output to `/dist`. Use `--no-minify` to skip minification. This is also the build behind the [source-deploy API endpoints](/docs/full_code_apps/deployment#deploy-from-sources-via-the-api), so an app deployed from sources through the API is compiled exactly as the CLI and the editor compile it. ### Generate lock files Generate `.lock` files for backend runnables with dependencies using the [`wmill generate-metadata`](./generate-metadata.md) command: ```bash wmill generate-metadata ``` To only update app lockfiles, use: ```bash wmill generate-metadata --skip-scripts --skip-flows ``` Options: `--yes`, `--dry-run`, `--default-ts`. :::info Legacy command Prior to the unified command, this was done with `wmill app generate-locks`. This command is now deprecated but still works. ::: ### Generate agent documentation From the app directory: ```bash wmill app generate-agents ``` Generates `AGENTS.md` and `DATATABLES.md` for AI coding agent context. ## Remote path format ```js //... ``` --- ## Datatable Source: https://www.windmill.dev/docs/advanced/cli/datatable # Datatables The `wmill datatable` commands let you query and manage [data tables](../../core_concepts/11_persistent_storage/data_tables.mdx) from the CLI. You can list available datatables, run SQL queries, version their schema with [migrations](#managing-migrations), or start an interactive PostgreSQL session. ## Listing datatables List all datatables in the current workspace. ```bash wmill datatable list [options] ``` ### Options | Option | Description | | -------- | --------------------------------- | | `--json` | Output as JSON (for piping to jq) | ### Example ```bash wmill datatable list ``` Output: ``` +------------+---------------+---------------+ | Name | Resource Type | Resource Path | +------------+---------------+---------------+ | main | postgresql | f/db/main | | analytics | postgresql | f/db/stats | +------------+---------------+---------------+ ``` ## Running a query Execute a SQL query against a datatable and display results. ```bash wmill datatable run [options] ``` ### Arguments | Argument | Description | | -------- | ---------------------------- | | `sql` | The SQL query to execute | ### Options | Option | Parameters | Description | | ------------------- | ---------- | ----------------------------------------------------- | | `-n, --name` | `name` | Datatable name to query (default: `main`) | | `-s, --silent` | | Output only the final result as JSON (for scripting) | ### Examples 1. Basic query: ```bash wmill datatable run "SELECT * FROM users LIMIT 10" ``` 2. Query a specific datatable: ```bash wmill datatable run -n analytics "SELECT COUNT(*) as count FROM events" ``` 3. Silent mode for scripting: ```bash wmill datatable run -s "SELECT version()" | jq '.version' ``` ## Managing migrations The `wmill datatable migrate` commands let you version the schema of a datatable with SQL migrations. Each migration is a pair of `.up.sql` / `.down.sql` files stored under `migrations/datatable//` and applied in timestamp order. Applied migrations are tracked in a `_wm_migrations` table inside each datatable, so a migration is never run twice. Migrations are ordinary [workspace files](./sync.mdx): `wmill sync pull` writes them locally, and `wmill sync push` upserts (or deletes) them on the remote workspace. You can also manage the same migrations from the UI in `workspace settings` -> `Data Tables` -> `Migrations`. See the [data tables migrations](../../core_concepts/11_persistent_storage/data_tables.mdx#migrations) documentation for the overall workflow. ### Scaffolding a migration Create an empty `.up.sql` / `.down.sql` migration pair. This is a purely local operation - no network call. ```bash wmill datatable migrate new [options] ``` The name may only contain letters, digits, `_` and `-`. The files are written to `migrations/datatable//_.up.sql` (and `.down.sql`), where `` is the current UTC time as `YYYYMMDDHHMMSS`. #### Arguments | Argument | Description | | -------- | -------------------------------------- | | `name` | Name of the migration (used in the filename and DB record) | #### Options | Option | Parameters | Description | | ------------------- | ------------ | ------------------------------------------ | | `-d, --datatable` | `datatable` | Target datatable (default: `main`) | #### Example ```bash wmill datatable migrate new add_users_table ``` Then edit the generated files, for example: ```sql -- migrations/datatable/main/20260617120000_add_users_table.up.sql BEGIN; CREATE TABLE users ( id BIGINT PRIMARY KEY, name TEXT NOT NULL ); END; ``` ```sql -- migrations/datatable/main/20260617120000_add_users_table.down.sql BEGIN; DROP TABLE users; END; ``` The `.down.sql` file is optional: leave it empty (or delete it) for migrations you never intend to roll back. ### Applying migrations Apply every pending migration, in timestamp order, to a datatable. ```bash wmill datatable migrate up [options] ``` Without a `--datatable` flag, `up` targets the `main` datatable. Any migration files created or edited locally are pushed to the workspace first, so `migrate up` works even before a `wmill sync push`. #### Options | Option | Parameters | Description | | ------------------- | ------------ | ------------------------------------------ | | `-d, --datatable` | `datatable` | Target datatable (default: `main`) | #### Examples 1. Apply pending migrations to the `main` datatable: ```bash wmill datatable migrate up ``` 2. Apply pending migrations to a specific datatable: ```bash wmill datatable migrate up -d analytics ``` ### Rolling back migrations Roll back the most recently applied migration (one step), running its `.down.sql`. ```bash wmill datatable migrate down [options] ``` Without a `--datatable` flag, `down` rolls back the latest migration on the `main` datatable. #### Options | Option | Parameters | Description | | ------------------- | ------------ | ------------------------------------------ | | `-d, --datatable` | `datatable` | Target datatable (default: `main`) | #### Example ```bash wmill datatable migrate down -d analytics ``` ### Syncing migrations Migrations sync like any other workspace item: - `wmill sync pull` writes migration files under `migrations/datatable//`. - `wmill sync push` upserts changed migrations and deletes those removed locally. When the push introduces new migrations, the CLI offers to run them. Local migration sets are validated on push and rejected if two `.up` (or two `.down`) files share a timestamp, or if a `.down.sql` has no matching `.up.sql`. Migrations are not covered by the `includes` / `excludes` patterns, since `migrations/datatable/` sits outside the namespaces those are written against; set [`skipDatatableMigrations: true`](./wmill-yaml-reference.mdx#skipdatatablemigrations) in `wmill.yaml` to leave them out. Workspaces with [git sync](../11_git_sync/index.mdx#type-filters) enabled also commit them to the linked repository. A push only deletes migrations the current branch has committed at some point, so pushing from a checkout that predates the sync cannot wipe them from the workspace. ## Starting a PostgreSQL proxy server Start a PostgreSQL wire-protocol proxy that serves all datatables. This allows any Postgres-compatible client (psql, DBeaver, pgAdmin, etc.) to connect and query your datatables. ```bash wmill datatable serve [options] ``` ### Options | Option | Parameters | Description | | ------------ | ---------- | ----------------------------------------------------- | | `--port` | `port` | Port to listen on (default: first available 5433-5500)| | `--host` | `host` | Bind address (default: `127.0.0.1`) | | `--password` | `password` | Connection password (default: randomly generated) | ### Behavior - Implements the PostgreSQL wire protocol (read-only queries only) - Each datatable appears as a separate database in the connection - Supports prepared statements and parameterized queries - Emulates Postgres system tables (like `pg_database`) for tool compatibility ### Example ```bash wmill datatable serve ``` Output: ``` Serving datatables on 127.0.0.1:5433 via Postgres wire protocol Available datatables: psql 'postgresql://wmill:a1b2c3d4e5f6@127.0.0.1:5433/main' psql 'postgresql://wmill:a1b2c3d4e5f6@127.0.0.1:5433/analytics' Press Ctrl+C to stop. ``` Connect with any Postgres client using the connection string shown. ## Interactive psql session Start a proxy server and launch an interactive `psql` session connected to it. ```bash wmill datatable psql [options] ``` ### Options | Option | Parameters | Description | | ------------------- | ---------- | ----------------------------------------------------- | | `-n, --name` | `name` | Datatable to connect to (default: `main`) | | `--port` | `port` | Port for the proxy (default: first available 5433-5500)| | `--host` | `host` | Bind address for the proxy (default: `127.0.0.1`) | | `--password` | `password` | Connection password (default: randomly generated) | ### Prerequisites The `psql` client must be installed: - **Linux**: `apt install postgresql-client` - **macOS**: `brew install libpq` ### Examples 1. Interactive session with the default datatable: ```bash wmill datatable psql ``` 2. Connect to a specific datatable: ```bash wmill datatable psql -n analytics ``` ## Summary | Command | Purpose | | ---------------- | ---------------------------------------------- | | `list` | List all datatables in the workspace | | `run` | Execute a single SQL query | | `migrate new` | Scaffold a new `.up.sql` / `.down.sql` migration | | `migrate up` | Apply all pending migrations | | `migrate down` | Roll back the most recent migration | | `serve` | Start a Postgres proxy for external clients | | `psql` | Launch an interactive psql session | --- ## Dev preview Source: https://www.windmill.dev/docs/advanced/cli/dev-preview # Dev & Preview The CLI can run scripts and flows against a remote workspace without deploying them, and it can serve a live-reload dev page that mirrors your local files. This is useful for iterating on local files, validating codebase scripts, or testing flow / app changes with local inline script modifications. ## `wmill dev` `wmill dev` starts a local live-reload server and opens a Windmill dev page that renders your local flow, script, or raw app. Edits on disk reload the page; edits in the page round-trip back to the local files (`flow.yaml`, inline scripts, `app.yaml`, etc.). ```bash wmill dev [--path ] [--proxy-port ] [--no-open] ``` When invoked from inside a `*__flow/` folder, `wmill dev` auto-detects the wm path. When invoked from a workspace root with no `--path`, the dev page shows a workspace picker — flows, scripts, and raw apps grouped by folder / user, with search and a kind filter. ### Run modes - **Direct** (default): a WebSocket server on a random port. The dev page lives on the remote workspace and connects back to localhost over WebSocket for live reload. This is the mode the [VS Code extension](../../cli_local_dev/1_vscode-extension/index.mdx) iframe and a regular browser tab use. - **Proxy** (`--proxy-port `): a reverse proxy at `http://localhost:/` that 302-redirects `/` to the remote dev page with workspace, auth token, and `--path` baked in, and tunnels other HTTP / WebSocket traffic to the remote workspace. This is the mode [Claude Code](../../misc/9_guides/local_dev_with_ai/index.mdx)'s port-detection preview pane uses. ### Options | Option | Description | | ----------------------- | -------------------------------------------------------------------------------------------- | | `--path ` | Open a specific flow, script, or raw app instead of the workspace picker. | | `--proxy-port ` | Run in proxy mode on the given port; needed for Claude Code's preview pane. | | `--no-open` | Don't open the browser; print the URL so the caller (e.g. an AI agent) can hand it to you. | ### Examples ```bash # Workspace picker wmill dev # Jump straight into a flow wmill dev --path f/my_flows/etl # Auto-detected when run from inside a flow folder cd f/my_flows/etl__flow && wmill dev # Proxy mode for Claude Code's preview pane wmill dev --proxy-port 4000 --path f/my_flows/etl ``` ### Round-trip editing Editing a flow or script from the dev page writes back to disk: - `flow.yaml` is rewritten only when content actually differs. - Inline scripts (`.py`, `.ts`, `.bun.ts`, `.go`, `.sh`, etc.) are kept in sync. - `PathScript` references (modules with `type: 'script'` and a `path:`) survive the round-trip. - Fixture files inside the flow folder (`README.md`, `data.json`, `.env`, etc.) are preserved. When `--path` is set, edits to other files don't push to the page, so you can keep working on unrelated files in the same workspace folder. :::tip Data pipelines [Pipelines](../../core_concepts/63_pipelines/index.mdx) have their own analog: `wmill pipeline dev` watches a folder of `// pipeline` scripts and live-previews the pipeline graph in the browser on every save. See [Pipelines (CLI)](./pipeline.md#live-preview-with-wmill-pipeline-dev). ::: ## `wmill script preview` Preview a local script file against the remote workspace. Supports both regular scripts and [codebase](../../core_concepts/33_codebases_and_bundles/index.mdx) scripts (which are bundled before running). ```bash wmill script preview [options] ``` ### Options | Option | Description | | ------------------ | ----------------------------------------------------------------- | | `-d, --data `| Inputs as a JSON string, `@filename`, or `@-` for stdin. | | `-s, --silent` | Only output the final result (no logs, useful for scripting). | | `--tag ` | Override the [worker tag](../../core_concepts/9_worker_groups/index.mdx#set-tags-to-assign-specific-queues) the preview is dispatched to, instead of the script's default tag. Useful to test changes on specific workers (for example dev workers) before deploying. | ### Examples ```bash # Regular script wmill script preview u/admin/my_script.ts --data '{"x": 5}' # Route the preview to dev workers instead of the script's default tag wmill script preview u/admin/my_script.ts --data '{"x": 5}' --tag dev # Codebase script (bundled before preview) wmill script preview f/codebase_test/my_script.ts --data '{"x": 7}' # Silent mode for piping wmill script preview f/scripts/hello.ts -d '{"n":3}' --silent | jq ``` ## `wmill flow preview` Preview a local flow against the remote workspace. The flow definition is read from the local `.flow` / `__flow` folder — any changes to inline scripts are picked up without deploying. ```bash wmill flow preview [options] ``` ### Options Same `-d, --data`, `-s, --silent`, and `--tag` options as `script preview`. `--tag` overrides the [worker tag](../../core_concepts/9_worker_groups/index.mdx#set-tags-to-assign-specific-queues) the preview is dispatched to (and is threaded through to individual steps when combined with `--step`). ### Example ```bash wmill flow preview f/my_flows/etl__flow --data '{"date":"2026-01-01"}' # Route the preview to dev workers instead of the flow's default tag wmill flow preview f/my_flows/etl__flow --data '{"date":"2026-01-01"}' --tag dev ``` Use `wmill dev` when you want a live UI that round-trips edits back to disk; use `wmill flow preview` when you want a one-shot run with a specific payload. --- ## Environment specific items Source: https://www.windmill.dev/docs/advanced/cli/environment-specific-items # Workspace-specific items 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 ` — 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 --- ## Flow Source: https://www.windmill.dev/docs/advanced/cli/flow # Flows ## Listing flows The `wmill flow` list command is used to list all flows in the remote workspace. ```bash wmill flow ``` ## Pushing a flow Pushing a flow to a Windmill instance is done using the `wmill flow push` command. ```bash wmill flow push ``` ### Arguments | Argument | Description | | ------------- | -------------------------------------------------------------- | | `file_path` | The path to the flow file to push. | | `remote_path` | The remote path where the flow specification should be pushed. | ### Examples 1. Push the flow located at `path/to/local/flow.yaml` to the remote path `f/flows/test`. ```bash wmill flow push path/to/local/flow.yaml f/flows/test ``` ## Creating a new flow The wmill flow bootstrap command is used to create a new flow locally. ```bash wmill flow bootstrap [--summary ] [--description ] ``` ### Arguments | Argument | Description | | ---------- | ------------------------------------ | | `path` | The path of the flow to be created. | ### Examples 1. Create a new flow `f/flows/flashy_flow` ```bash wmill flow bootstrap f/flows/flashy_flow ``` ## Running a flow Running a flow by its path is done using the `wmill flow run` command. Logs are streamed step-by-step with labeled headers showing each module's ID and summary. For-loop iterations are tracked individually as they complete. ```bash wmill flow run [options] ``` ### Arguments | Argument | Description | | ------------- | ------------------------------- | | `remote_path` | The path of the flow to be run. | ### Options | Option | Parameters | Description | | -------------- | ---------- | ----------------------------------------------------------------------------- | | `-d, --data` | `data` | Inputs specified as a JSON string or a file using @filename or stdin using @- . Resources and variables must be passed using "$res:..." or "$var:..." For example `wmill flow run u/henri/message_to_slack -d '{"slack":"$res:u/henri/henri_slack_perso","channel":"general","text":"hello dear team"}'` | | `-s, --silent` | | Do not output anything other then the final output. Useful for scripting. | | `--tag` | `tag` | Override the [worker tag](../../core_concepts/9_worker_groups/index.mdx#set-tags-to-assign-specific-queues) the run is dispatched to, instead of the flow's default tag. Useful to route a run to specific workers (for example dev workers instead of prod). | ![CLI arguments](../../assets/cli/cli_arguments.png "CLI arguments") :::tip Inspecting flow runs after completion Use `wmill job get ` to see a hierarchical step tree with status and durations, or `wmill job logs ` to see aggregated logs from all steps. See [Jobs](./job.md) for details. ::: ## Update flow inline scripts lockfile Flows inline script [lockfiles](../6_imports/index.mdx) can be updated locally using the [`wmill generate-metadata`](./generate-metadata.md) command: ```bash wmill generate-metadata ``` This command handles scripts, flows and apps in a single pass. To only update flow lockfiles, use: ```bash wmill generate-metadata --skip-scripts --skip-apps ``` :::info Legacy command Prior to the unified command, this was done with `wmill flow generate-locks`. This command is now deprecated but still works. ::: ## Flow specification You can find the definition of the flow file structure [here](../../openflow/index.mdx). ## Remote path format ```js //... ``` --- ## Folder Source: https://www.windmill.dev/docs/advanced/cli/folder # Folder ## Listing folders The `wmill folder` list command is used to list all folders in the remote workspace. ```bash wmill folder ``` ## Push The `wmill folder push` command is used to push a local folder specification to a remote location, overriding any existing remote versions. ```bash wmill folder push ``` ### Arguments | Argument | Description | | ------------- | ---------------------------------------------------------------- | | `folder_path` | The path to the local folder. | | `remote_path` | The path to the remote location where the folder will be pushed. | ## Adding missing folders When you create scripts, flows, or apps under `f//` without a corresponding `f//folder.meta.yaml` file, `wmill sync push` will either warn (admins) or fail (non-admins, due to RLS) because the remote folder does not exist. The `wmill folder add-missing` command scans every `f//` subdirectory and creates a default `folder.meta.yaml` for any that are missing one. ```bash wmill folder add-missing [-y] ``` ### Options | Option | Description | | ------ | ---------------------------------- | | `-y` | Skip the confirmation prompt. | ### Example ```bash # Dry scan, then create the missing files after confirmation wmill folder add-missing # Non-interactive (useful in scripts) wmill folder add-missing -y ``` `wmill sync push` runs the same detection automatically and suggests `wmill folder add-missing` when folders are missing. ## Stale script detection `wmill generate-metadata` builds a dependency tree across scripts, flows, apps, and [workspace dependencies](../../core_concepts/55_workspace_dependencies/index.mdx), then propagates staleness along that graph. This means if script `C` changes, any scripts `A` and `B` that transitively import `C` (including via relative imports like `./helper` or `../shared/utils`) are correctly detected as stale and regenerated. When you run `wmill generate-metadata f/lib`, scripts **outside** `f/lib` that import from it are included in the run by default. Use `--strict-folder-boundaries` to restrict the run to items physically inside the folder — excluded items that would otherwise have been regenerated are printed as warnings. See [`wmill generate-metadata`](./generate-metadata.md) for the full option list. --- ## Generate metadata Source: https://www.windmill.dev/docs/advanced/cli/generate-metadata # Generate metadata The `wmill generate-metadata` command generates metadata (locks, schemas) for all scripts, flows and apps. It replaces the previous separate commands (`wmill script generate-metadata`, `wmill flow generate-locks`, `wmill app generate-locks`). ## Usage ```bash wmill generate-metadata [folder] [options] ``` ## Options | Option | Description | | ------ | ----------- | | `--yes` | Skip confirmation prompt | | `--dry-run` | Show what would be updated without making changes | | `--lock-only` | Regenerate only lock files | | `--schema-only` | Regenerate only script schemas (skips flows and apps) | | `--skip-scripts` | Skip processing scripts | | `--skip-flows` | Skip processing flows | | `--skip-apps` | Skip processing apps | | `--strict-folder-boundaries` | Only update items inside the specified folder (requires folder argument) | | `-i, --includes ` | Comma-separated patterns to specify which files to include | | `-e, --excludes ` | Comma-separated patterns to specify which files to exclude | The `rehash` subcommand (see [Rehash](#rehash) below) rewrites lockfile hashes from on-disk content without any backend round-trip. ## Arguments | Argument | Description | | -------- | ----------- | | `folder` | Optional folder path to filter metadata generation | ## Examples ### Generate metadata for entire workspace ```bash wmill generate-metadata ``` ### Preview changes without applying ```bash wmill generate-metadata --dry-run ``` ### Auto-confirm all updates ```bash wmill generate-metadata --yes ``` ### Generate only for a specific folder ```bash wmill generate-metadata f/my_folder ``` ### Strict folder boundaries ```bash wmill generate-metadata f/my_folder --strict-folder-boundaries ``` ### Only update lockfiles ```bash wmill generate-metadata --lock-only ``` ### Only update schemas ```bash wmill generate-metadata --schema-only ``` ### Include specific patterns ```bash wmill generate-metadata -i "f/production/*,f/shared/*" ``` ### Exclude specific patterns ```bash wmill generate-metadata -e "f/test/*,f/drafts/*" ``` ## Rehash `wmill generate-metadata rehash [folder]` rewrites `wmill-lock.yaml` hashes directly from on-disk content. It does not contact the backend, does not regenerate scripts/flows/apps, and does not rewrite YAML files — it only recomputes the staleness hashes. Use it when: - **`wmill generate-metadata` flags items as stale that you didn't change.** Common causes: an older CLI wrote the lockfile with a different YAML library or unsorted hash keys, leaving entries that no longer match what the current CLI computes. `rehash` rebuilds those entries from disk so they line up again. - **Your `wmill-lock.yaml` has duplicate `./`-prefixed entries** (e.g. `./f/foo+__script_hash` next to `f/foo+__script_hash`). These come from past `wmill generate-metadata ./folder` invocations on older CLIs. `rehash` deduplicates them by writing canonical (non-prefixed) keys. - **You bootstrapped a long-lived `wmill sync`-managed repo** and want to fill in lockfile entries for items that pre-date the lockfile, without redeploying everything. ### Options | Option | Description | | ------ | ----------- | | `--skip-scripts` | Skip processing scripts | | `--skip-flows` | Skip processing flows | | `--skip-apps` | Skip processing apps | | `-i, --includes ` | Comma-separated patterns to specify which files to include | | `-e, --excludes ` | Comma-separated patterns to specify which files to exclude | ### Examples ```bash # Rebuild every lockfile entry from disk wmill generate-metadata rehash # Scope to a single folder wmill generate-metadata rehash f/billing # Rehash scripts only wmill generate-metadata rehash --skip-flows --skip-apps ``` `rehash` is non-destructive: it never touches your scripts, flows, apps, or YAML files. It only writes to `wmill-lock.yaml`. :::caution Trust, don't verify `rehash` accepts the on-disk content as canonical without checking it against the backend. Don't use it as a substitute for a regular `wmill generate-metadata` run when you actually want to validate that locks/schemas are up to date. Use it only when you've already confirmed the disk content is what you want and just need the lockfile to stop reporting it as stale. ::: `wmill sync pull` runs an automatic, lighter version of this for items that exist on disk but have no lockfile entry — so for typical pull-then-push workflows you don't need to invoke `rehash` manually. The auto-fill is skipped under `--json` and `--dry-run` so non-interactive scripts and previews don't mutate `wmill-lock.yaml`. ## Migration from legacy commands The following commands are now deprecated: | Deprecated command | Replacement | | ------------------ | ----------- | | `wmill script generate-metadata` | `wmill generate-metadata --skip-flows --skip-apps` | | `wmill flow generate-locks` | `wmill generate-metadata --skip-scripts --skip-apps` | | `wmill app generate-locks` | `wmill generate-metadata --skip-scripts --skip-flows` | The legacy commands will show deprecation warnings but continue to work. For centralized dependency management, see [workspace dependencies](../../core_concepts/55_workspace_dependencies/index.mdx). --- ## Gitsync settings Source: https://www.windmill.dev/docs/advanced/cli/gitsync-settings # Git sync settings Managing git-sync configuration between your local `wmill.yaml` file and the Windmill workspace backend is made easy using the wmill CLI. Git-sync settings operations are behind the `wmill gitsync-settings` subcommand. The `gitsync-settings` command allows you to synchronize filter settings, path configurations, and branch-specific overrides without affecting the actual workspace content (scripts, flows, apps, etc.). Git-sync settings management is done using `wmill gitsync-settings pull` & `wmill gitsync-settings push`. Settings operations are configuration-only and work with your `wmill.yaml` file structure. The command will show you the list of changes and ask for confirmation before applying them. When pulling, the source is the remote workspace git-sync configuration and the target is your local `wmill.yaml` file, and when pushing, the source is your local `wmill.yaml` and the target is the remote workspace configuration. ## Pull API The `wmill gitsync-settings pull` command is used to pull remote git-sync settings and apply them to your local `wmill.yaml` file. It synchronizes your local configuration with the workspace git-sync settings. ```bash wmill gitsync-settings pull [options] ``` ### Options | Option | Parameter | Description | | ----------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-h, --help` | None | Show help options. | | `--repository` | `` | Specify repository path (e.g., u/user/repo). If not specified, will auto-select if only one repository exists or prompt for selection. | | `--default` | None | Write settings to top-level defaults instead of branch-specific overrides. | | `--replace` | None | Replace existing settings (non-interactive mode). Overwrites top-level wmill.yaml settings. | | `--override` | None | Add workspace-specific override (non-interactive mode). Creates or updates an entry under the `workspaces:` section. | | `--diff` | None | Show differences without applying changes. Preview what would be modified in your local configuration. | | `--json-output` | None | Output in JSON format. Useful for scripting and automation. | | `--with-backend-settings` | `` | Use provided JSON settings instead of querying backend (primarily for testing purposes). | | `--yes` | None | Skip interactive prompts and use default behavior. The command proceeds automatically without user intervention. | | `--promotion` | `` | Use promotionOverrides from the specified branch instead of regular overrides. | | `--workspace` | `` | Specify the target workspace. This overrides the default workspace. | | `--debug`, `--verbose` | None | Show debug/verbose logs. | | `--token` | `` | Specify an API token. This will override any stored token. | | `--base-url` | `` | Specify the base URL of the API. If used, `--token` and `--workspace` are required and no local remote/workspace will be used. | ## Push API The `wmill gitsync-settings push` command is used to push local git-sync settings and apply them to the remote workspace configuration. It synchronizes the workspace git-sync settings with your local `wmill.yaml`. ```bash wmill gitsync-settings push [options] ``` ### Options | Option | Parameter | Description | | ----------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-h, --help` | None | Show help options. | | `--repository` | `` | Specify repository path (e.g., u/user/repo). If not specified, will auto-select if only one repository exists or prompt for selection. | | `--diff` | None | Show what would be pushed without applying changes. Preview the modifications that would be made to the workspace configuration. | | `--json-output` | None | Output in JSON format. Useful for scripting and automation. | | `--with-backend-settings` | `` | Use provided JSON settings instead of querying backend (primarily for testing purposes). | | `--yes` | None | Skip interactive prompts and use default behavior. The command proceeds automatically without user intervention. | | `--promotion` | `` | Use promotionOverrides from the specified branch instead of regular overrides. | | `--workspace` | `` | Specify the target workspace. This overrides the default workspace. | | `--debug`, `--verbose` | None | Show debug/verbose logs. | | `--token` | `` | Specify an API token. This will override any stored token. | | `--base-url` | `` | Specify the base URL of the API. If used, `--token` and `--workspace` are required and no local remote/workspace will be used. | ## Status API Local changes reach the workspace through one of two paths, and which one applies depends on how the repository is wired: - `git push` - when the workspace deploys new commits itself ([automatic sync from Git](../11_git_sync/index.mdx#automatic-sync-from-git), available on [Enterprise Edition](/pricing)), or when a CI workflow runs `wmill sync push` on push. - [`wmill sync push`](./sync.mdx#push-api) - when there is no such deploy-on-push path. The `wmill gitsync-settings status` command reports which path applies to your workspace and current branch, so you don't have to guess: ```bash wmill gitsync-settings status [options] ``` It reads your current local git branch and asks the backend whether pushing it is deployed by automatic sync from Git. A `git push` is confirmed to deploy only when exactly one repository with automatic sync enabled tracks the branch. Otherwise the result is reported as not confirmed (rather than a definite no), since a CI workflow the command doesn't recognize could still deploy on push - in that case, determine the deploy path from your CI configuration or use `wmill sync push`. The endpoint returns only derived booleans - no repository paths, credentials, or webhook configuration - so any workspace member (not just admins) can run it. ### Options | Option | Parameter | Description | | ---------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `-h, --help` | None | Show help options. | | `--json-output` | None | Output in JSON format. Useful for scripting and automation. | | `--workspace` | `` | Specify the target workspace. This overrides the default workspace. | | `--token` | `` | Specify an API token. This will override any stored token. | | `--base-url` | `` | Specify the base URL of the API. If used, `--token` and `--workspace` are required and no local remote/workspace will be used. | ### JSON output With `--json-output`, the command prints the deploy mode and a recommended command: ```json { "configured": true, "deploy_on_push": true, "current_branch": "main", "deploy_command": "git push" } ``` - `configured` - at least one git-sync repository is configured for the workspace. - `deploy_on_push` - `true` only when pushing `current_branch` is confirmed to deploy via automatic sync from Git. - `current_branch` - the local git branch checked (or `null` outside a git repository). - `deploy_command` - `git push` when `deploy_on_push` is `true`, otherwise `null`. ## Configuration management The `gitsync-settings` command manages git-sync filter settings and configuration options defined in your `wmill.yaml` file and the workspace backend. ### Settings managed The command handles these git-sync configuration fields (see [wmill.yaml](./sync.mdx#wmillyaml) for complete details): - **Path filters**: `includes`, `excludes`, `extraIncludes` - control which files are synced based on path patterns - **Type filters**: Control which types of resources are synced: - `skipScripts`, `skipFlows`, `skipApps`, `skipFolders` - resource type filters - `skipVariables`, `skipResources`, `skipResourceTypes`, `skipSecrets` - additional type filters - `includeSchedules`, `includeTriggers`, `includeUsers`, `includeGroups` - optional inclusions - `includeSettings`, `includeKey` - system resource inclusions ### Configuration modes The pull command supports different write modes when conflicts exist: - **Replace mode** (`--replace`): Overwrites top-level wmill.yaml settings - **Override mode** (`--override`): Creates workspace-specific overrides in the `workspaces:` section - **Default mode** (`--default`): Writes to top-level defaults - **Interactive mode** (default): Prompts user to choose between replace/override when conflicts exist ## Usage examples ### Basic operations ```bash # Pull git-sync settings from workspace to local wmill.yaml wmill gitsync-settings pull # Push local wmill.yaml git-sync settings to workspace wmill gitsync-settings push # Work with specific repository wmill gitsync-settings pull --repository u/user/my-repo wmill gitsync-settings push --repository u/user/my-repo ``` ### Preview changes ```bash # Show what would change in local configuration wmill gitsync-settings pull --diff # Show what would be pushed to workspace wmill gitsync-settings push --diff # JSON output for scripting wmill gitsync-settings pull --diff --json-output ``` ### Configuration modes ```bash # Replace top-level settings (non-interactive) wmill gitsync-settings pull --replace # Add workspace-specific override (non-interactive) wmill gitsync-settings pull --override # Write to defaults section wmill gitsync-settings pull --default # Skip prompts, use default behavior wmill gitsync-settings pull --yes ``` ### Branch-specific operations ```bash # Use promotion-specific overrides from main branch wmill gitsync-settings pull --promotion main # Push promotion settings to workspace wmill gitsync-settings push --promotion production ``` When using the `--promotion` flag, the command will use `promotionOverrides` instead of regular `overrides` from the specified branch in your `wmill.yaml`. This should be used for [Git Promotion](../9_deploy_gh_gl/index.mdx) connections. ## Differences from sync command The `gitsync-settings` command is specifically for managing configuration, not workspace content: | Feature | `gitsync-settings` | `sync` | |---------|-------------------|---------| | **Purpose** | Manages git-sync configuration settings | Synchronizes actual workspace content (scripts, flows, etc.) | | **Scope** | Configuration metadata only | Workspace resources and files | | **Target** | `wmill.yaml` git-sync settings | Workspace scripts, flows, apps, resources | | **File Operations** | Modifies `wmill.yaml` structure | Creates/updates resource files in sync directory | | **Safety** | Configuration changes only | Can create/delete workspace resources | For more details on the `wmill.yaml` configuration structure, see [wmill.yaml](./sync.mdx#wmillyaml). --- ## Installation Source: https://www.windmill.dev/docs/advanced/cli/installation # Installation To install the wmill CLI: ```bash npm install -g windmill-cli ``` Node version must greater than v20. Also, to punch through some networking layers like Cloudflare Tunnel, you might need some custom headers. You just need to use the HEADERS env variable: ``` export HEADERS=header_key:header_value,header_key2:header_value2 ``` Verify that the installation was successful by running the following command: ```bash wmill --version ``` If the installation was successful, you should see the version of wmill that you just installed. ## Upgrade wmill To upgrade your wmill installation to the latest version, run the following command: ```bash wmill upgrade ``` ## Completion The CLI comes with built-in completions for various shells. Use the following instructions to enable completions for your preferred shell. ### Bash To enable bash completions, add the following line to your ~/.bashrc: ```bash source <(wmill completions bash) ``` ### Zsh To enable zsh completions, add the following line to your ~/.zshrc: ```bash source <(wmill completions zsh) ``` ### Fish To enable fish completions, add the following line to your ~/.config/fish/config.fish: ```bash source (wmill completions fish | psub) ``` --- ## Job Source: https://www.windmill.dev/docs/advanced/cli/job # Jobs The `wmill job` commands let you list, inspect, and manage jobs from the CLI. For flow jobs, `job get` shows a hierarchical step tree and `job logs` aggregates logs from all steps. ## Listing jobs List recent jobs in the workspace. ```bash wmill job list [options] ``` ### Options | Option | Parameters | Description | | ------------------- | ------------- | --------------------------------------------------------------------------- | | `--json` | | Output as JSON (for piping to jq). | | `--script-path` | `path` | Filter by exact script or flow path. | | `--created-by` | `username` | Filter by creator username. | | `--running` | | Show only running jobs. | | `--failed` | | Show only failed jobs. | | `--limit` | `number` | Number of jobs to return (default 30, max 100). | | `--all` | | Include sub-jobs (flow steps). By default only top-level jobs are shown. | | `--parent` | `id` | Show only sub-jobs of a specific flow job. | | `--is-flow-step` | | Show only flow step jobs. | ### Examples 1. List recent failed jobs: ```bash wmill job list --failed ``` 2. List jobs for a specific flow: ```bash wmill job list --script-path f/production/etl_pipeline ``` 3. List sub-jobs of a flow run: ```bash wmill job list --parent 019d447b-114f-a018-0b72-9e541fb77c02 ``` ## Getting job details Get details about a specific job. For flow jobs, this displays a hierarchical step tree showing each module's status, label, duration, and sub-job ID. ```bash wmill job get [options] ``` ### Options | Option | Description | | -------- | -------------------------------- | | `--json` | Output as JSON (for piping to jq). | ### Example ```bash wmill job get 019d447b-114f-a018-0b72-9e541fb77c02 ``` For a flow job, the output includes a step tree: ``` ID: 019d447b-114f-a018-0b72-9e541fb77c02 Type: flow Status: success ... Steps: ✓ a: Generate list (019d447b-2a3f-...) 1.2s ✓ b: Process items (019d447b-3b4c-...) 3.4s ✓ iteration 0 (019d447b-4c5d-...) 1.1s ✓ iteration 1 (019d447b-5d6e-...) 1.2s ✓ iteration 2 (019d447b-6e7f-...) 1.1s ✓ c: Aggregate (019d447b-7f80-...) 0.8s ``` ## Getting job results Get the result of a completed job as JSON. Useful for scripting and piping to other commands. ```bash wmill job result ``` ## Getting job logs Get logs for a job. For flow jobs, this aggregates logs from all steps with labeled headers. For-loop iterations are shown individually. ```bash wmill job logs ``` ### Example For a flow job: ```bash wmill job logs 019d447b-114f-a018-0b72-9e541fb77c02 ``` ``` ====== a: Generate list ====== generating 3 items... ====== b: Process items (iteration 0) ====== processing item 1... ====== b: Process items (iteration 1) ====== processing item 2... ====== c: Aggregate ====== aggregating results... ``` For a specific step, use the sub-job ID from `job get`: ```bash wmill job logs 019d447b-2a3f-... ``` ## Cancelling a job Cancel a running or queued job. ```bash wmill job cancel [options] ``` ### Options | Option | Parameters | Description | | ---------- | ---------- | ------------------------ | | `--reason` | `reason` | Reason for cancellation. | ## Flow debugging workflow A typical workflow for debugging a failed flow run: ```bash # 1. Find the flow job wmill job list --script-path f/production/etl_pipeline --failed # 2. Inspect the step tree to see which step failed wmill job get # 3. See all step logs at once wmill job logs # 4. Or dive into a specific step's logs wmill job logs ``` --- ## Lint Source: https://www.windmill.dev/docs/advanced/cli/lint # Lint The `wmill lint` command validates Windmill YAML files (flows, schedules, and triggers) against their schemas. It scans a directory tree, infers each file's type from its filename (`.flow.yaml`, `.schedule.yaml`, `.http_trigger.yaml`, etc.), and reports missing required fields, unknown properties, or invalid values. ## Usage ```bash wmill lint [directory] [options] ``` If no directory is provided, the current directory is used. The command respects the `includes`/`excludes` patterns from your `wmill.yaml`. ## Options | Option | Description | | ------------------- | ------------------------------------------------------------------------------------------------ | | `--json` | Output machine-readable JSON instead of formatted text. | | `--fail-on-warn` | Exit non-zero on warnings (e.g. skipped native triggers). | | `--locks-required` | Fail if any script or inline script that needs a lock is missing one. | Native triggers (triggers whose schema depends on a dynamic `service_config`) are skipped with a warning — no static schema is available for them. ## Examples Lint every YAML file under the current directory: ```bash wmill lint ``` Lint a specific folder and emit JSON for CI tooling: ```bash wmill lint f/my_project --json ``` Fail the build if any script/flow/app inline script is missing its lockfile: ```bash wmill lint --locks-required ``` ## `--locks-required` `--locks-required` checks standalone scripts, flow inline scripts, normal app inline scripts, and raw app backend scripts. It applies to languages that need a lock: `bun`, `python3`, `php`, `go`, `deno`, `rust`, and `ansible`. The flag can also be enabled globally in `wmill.yaml`: ```yaml locksRequired: true ``` When set, `wmill sync push` runs the same verification before pushing, failing fast if anything is missing. ## CI integration A typical GitHub Actions step: ```yaml - name: Validate Windmill YAML files run: | npm install -g windmill-cli wmill lint --fail-on-warn --locks-required ``` Pair `wmill lint` with [`wmill generate-metadata`](./generate-metadata.md) to (re)generate any missing lockfiles before linting. --- ## Pipeline Source: https://www.windmill.dev/docs/advanced/cli/pipeline # Pipelines The `wmill pipeline` commands inspect and run [pipelines](../../core_concepts/63_pipelines/index.mdx): folders of scripts marked `// pipeline`, wired together by `// on ` annotations. All commands work against the deployed workspace by default; `show`, `run` and `docs` also accept `--local` to work from your working-tree files instead, and `wmill pipeline dev` live-previews local files in the browser (see [local development with --local](#local-development-with---local)). :::caution Alpha Pipelines are in alpha; the annotation syntax and behavior are still evolving. See the [Pipelines](../../core_concepts/63_pipelines/index.mdx) page for the full annotation reference. ::: ## Listing pipelines ```bash wmill pipeline list [--json] ``` Lists the pipeline folders in the workspace. `--json` outputs the list as JSON for piping to `jq`. ## Showing the pipeline graph ```bash wmill pipeline show [--json] [--local] ``` Renders the folder's pipeline DAG in the terminal: sources, asset lineage and subscriptions. | Option | Description | | --- | --- | | `--json` | Output the raw asset graph as JSON. | | `--local` | Build the graph from local working-tree files instead of the deployed workspace. Fully offline, no deploy needed. | ## Running a pipeline ```bash wmill pipeline run [options] ``` Runs a cascade: starting from `--from`, every downstream step runs in topological order, stopping at the `--to` end node(s) if given. `--from` may be any runnable node - a schedule/manual root or a mid-DAG model (asset subscriber or pure reader). A mid-DAG start runs that node plus its transitive downstream and never re-runs upstream, matching dbt's `--select model+`. This is the CLI counterpart of the graph's Run + downstream and [selective execution](../../core_concepts/63_pipelines/index.mdx#selective-execution-run-up-to-here). | Option | Description | | --- | --- | | `--from {#if loading}
Loading...
{:else} {#each users as user} {/each}
NameEmail
{user.name}{user.email}
{/if} ``` ![Full-code apps](../full_code_apps.png 'Full-code apps') ## Type safety During development, `wmill app dev` generates a `wmill.d.ts` file with typed signatures for each backend runnable. For example, given `backend/get_users.ts`: ```typescript export async function main(limit: number = 10): Promise { ... } ``` The generated types will be: ```typescript }; ``` This gives you autocomplete and type checking when calling runnables from your frontend. --- ## Project structure Source: https://www.windmill.dev/docs/full_code_apps/project_structure # Project structure A full-code app lives in a directory with the `.raw_app` suffix (e.g. `my_app.raw_app/`). If your filesystem or tooling hides dotfiles, you can use the `__raw_app` suffix instead (e.g. `my_app__raw_app/`) by setting `nonDottedPaths: true` in `wmill.yaml`. All examples below use the default `.raw_app` suffix. ## Directory layout ``` f/folder/my_app.raw_app/ ├── raw_app.yaml # App configuration (required) ├── package.json # Frontend dependencies ├── index.tsx # Frontend entry point ├── App.tsx # Main component (React example) ├── index.css # Styles ├── wmill.d.ts # Auto-generated TypeScript definitions ├── AGENTS.md # Auto-generated AI agent instructions ├── DATATABLES.md # Auto-generated data table documentation ├── backend/ # Backend runnables │ ├── get_users.ts # Runnable code │ ├── get_users.yaml # Runnable config (optional) │ ├── get_users.lock # Generated lock file │ └── query.pg.sql # SQL runnable (language inferred from extension) └── sql_to_apply/ # SQL migrations (dev only, not deployed) └── 001_create_table.sql ``` ## raw_app.yaml The `raw_app.yaml` file is the main configuration file for a full-code app. It defines metadata, execution policy and data table access. ```yaml summary: "My dashboard app" # Optional: custom URL path (admin only) custom_path: "my-dashboard" # Optional: make the app publicly accessible (no login) public: false # Optional: open the app to guests (an identity required, no Windmill account needed) guests: false # Optional: data table access configuration data: datatable: "main" # Datatable name tables: # Tables to whitelist - "users" - "orders" schema: "app1" # Schema within the datatable ``` The execution policy (including `triggerables` and `triggerables_v2`) is auto-generated at deployment time and does not need to be specified in `raw_app.yaml`. Its `execution_mode` comes from `public` and `guests`: `public: true` means `anonymous`, `guests: true` means `guest`, and neither means `publisher`. ### Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `summary` | string | Yes | Short description of the app (max 1000 chars) | | `custom_path` | string | No | Custom URL path (admin only) | | `public` | boolean | No | Whether the app is publicly accessible without login ([public app](../../apps/8_public_apps.mdx)) | | `guests` | boolean | No | Whether the app is open to [guests](../../apps/13_guest_apps/index.mdx): an identity is required, a Windmill account is not | | `data.datatable` | string | No | [Datatable](../../core_concepts/11_persistent_storage/data_tables.mdx) name | | `data.tables` | string[] | No | List of table names to whitelist | | `data.schema` | string | No | Schema name within the datatable | ## Auto-generated files These files are [generated by the CLI](../5_cli_workflow/index.mdx) and should not be edited manually: - **`wmill.d.ts`**: TypeScript definitions for the `wmill.ts` module, with typed signatures for each backend runnable. Regenerated when runnables change during `wmill app dev`. - **`AGENTS.md`**: Instructions for AI coding agents (Claude, Copilot, etc.) describing the project structure and available APIs. Generated by `wmill app generate-agents`. - **`DATATABLES.md`**: Documentation of available data tables and their schemas. Generated by `wmill app generate-agents`. ## Files excluded from deployment The following are not included when pushing to Windmill: - `node_modules/` - `dist/` - `.claude/`, `.git/` - `wmill.d.ts`, `package-lock.json` - `raw_app.yaml` (metadata is handled separately) - `sql_to_apply/` (dev-only migrations) - `DATATABLES.md`, `AGENTS.md` - `backend/` (runnables are extracted and uploaded separately) ## App path format App paths follow the standard Windmill path convention: ``` // ``` For example: `f/dashboard/my_app`, `u/admin/my_tool`, `g/engineering/ops_panel`. --- ## Sandbox isolation Source: https://www.windmill.dev/docs/full_code_apps/sandbox_isolation # Sandbox isolation Sandbox isolation renders a deployed app in an opaque-origin iframe, so the app's browser-side code cannot reach the viewer's Windmill session. It applies to both [low-code apps](../../apps/0_app_editor/index.mdx) and [full-code apps](../index.mdx). This happens in the viewer's browser, and is unrelated to the [process isolation](../../advanced/27_security_isolation/index.mdx) that sandboxes job execution on a worker. Turn it on from the app editor's Deploy panel, under Sandbox isolation. It is off by default and currently in alpha, and is [being made the norm in stages](#rollout). ![Sandbox isolation setting](./sandbox_toggle.webp 'Sandbox isolation setting') ## Why enable it An app runs code its author wrote, in the browser of whoever opens it. Without isolation that code runs on the Windmill origin with the viewer's session, so it acts as the viewer: a silent privilege escalation for a malicious app author. An XSS bug in an otherwise trusted app gives an attacker the same thing. Isolation removes that reach: - Low-code apps render in the opaque iframe with a scoped embed token instead of the session cookie. Its app scopes name that one app, so it cannot drive another. It lasts 12 hours and cannot mint a further token. - Full-code apps have their bundle served under a `sandbox` content security policy, which confines the bundle without needing a token. One is minted only if the app declares [frontend API access](#frontend-api-access), and that one is the viewer's. It applies wherever the app is reachable, on the [public URL](../../apps/8_public_apps.mdx) and in-workspace alike. We recommend turning it on. It also removes the warning viewers get before a public app renders HTML or SVG produced by a runnable. Isolation is a separate question from the app's [execution policy](../../apps/3_app-runnable-panel.mdx#policy), which decides which user the runnables execute as. The two are configured independently. ## Rollout Isolation is becoming the default in three stages: 1. Now: optional, off by default, and in alpha. 2. November 2026: on by default for newly created apps. Existing apps keep whatever they are set to. 3. January 2027: viewers see a warning when they load an app that is not sandboxed. Those dates are intentions, not commitments. Turning isolation on now means the later stages change nothing for your apps. While it is in alpha we are looking for feedback, particularly on apps it breaks. Tell us on [Discord](https://discord.com/invite/V7PM2YHsPB) or open a [GitHub issue](https://github.com/windmill-labs/windmill/issues). ## Limitations An opaque origin has no real web storage and no cookies. Windmill shims them so apps that use them keep working, but they are not the browser's own: - `localStorage` is backed by the hosting page, keyed per workspace and app, so two sandboxed apps never share a store. - `sessionStorage` is in-memory and lasts only for the session. - `document.cookie` is an in-memory jar, not the Windmill session cookie. Anything that needs a real origin does not work: IndexedDB, third-party authentication SDKs, and OAuth redirects that return to a named origin. Leave isolation off for apps that depend on those. The feature is in alpha, so after turning it on, open the app from its public URL and confirm it still behaves as expected. ## Frontend API access A separate opt-in, for [full-code apps](../index.mdx) only and only once isolation is on. A sandboxed bundle can no longer reach the API on the viewer's session, so if its frontend code needs the API, give it a credential of its own. Toggle the capabilities individually: running scripts and flows, reading jobs and results, reading the viewer's identity, reading [resources](../../core_concepts/3_resources_and_types/index.mdx), reading [variables](../../core_concepts/2_variables_and_secrets/index.mdx), and reading or managing the viewer's [flow chats](../../flows/26_flow_as_chat.mdx#chat-sdk-for-your-own-frontend). The token is minted for the viewer rather than the publisher and carries only what you toggled. It is a different credential from the one a sandboxed low-code app gets, and a broader one where it counts: running scripts and flows covers anything that viewer could run, and the resource and variable scopes expose credential values. Each viewer approves the list before the app runs, and can choose to open the app without granting it. Ticking "Do not ask again" skips the prompt on later visits, until the app declares a scope they have not already approved. ![Frontend API access consent screen](./sdk_consent.webp 'Frontend API access consent screen') Grant only what the app needs: its code, or an XSS bug in it, can use every granted scope as that viewer. Add `windmill-client` to the app's dependencies to make the calls, and note that an app bundled before frontend API access existed fails with a CORS error until you deploy it again. --- ## Ui editor Source: https://www.windmill.dev/docs/full_code_apps/ui_editor # UI editor Full-code apps can be created and edited directly in the Windmill web interface, without using the [Windmill CLI](../5_cli_workflow/index.mdx). ![Full-code apps](../full_code_apps.png 'Full-code apps') ## Overview The in-browser editor provides: - **File tree**: navigate and manage frontend files - **Code editor**: edit frontend and backend code with syntax highlighting - **Runnables panel**: create, edit and configure backend runnables from a sidebar - **Data table configuration**: whitelist data tables from the UI - **Preview**: test your app without deploying ## Creating a full-code app From your Windmill home page: 1. Click **New** and select **App (full-code)** to start from scratch, then choose your framework (React or Svelte). 2. To start from an existing project instead, hover **App (full-code)** in the **New** popover and click **Import full-code app**, then paste your YAML or JSON export in the drawer. ## Editing frontend files The file tree on the left shows all frontend files. Click a file to open it in the code editor. You can: - Create new files and folders - Rename and delete files - Edit code with syntax highlighting and autocompletion ## Managing backend runnables The runnables panel on the right lists all backend runnables. From here you can: - Add new inline scripts in any supported language - Reference existing workspace scripts or flows - Configure static input fields - View and edit runnable code ## Data table configuration Configure which [data tables](../4_data_tables/index.mdx) your app can access: 1. Open the data configuration panel 2. Select a datatable from your workspace 3. Whitelist specific tables 4. Optionally set a schema name ## Preview and testing Click the preview button to test your app in the browser. The preview runs your bundled frontend code and connects to Windmill's backend for runnable execution. In an [AI session](../../core_concepts/69_ai_sessions/index.mdx#debugging-full-code-apps), the AI can read the preview's rendered DOM, console logs and job runs to debug the app, and you can click elements in the preview to attach them to your chat message. The AI can also capture the preview with its [`take_screenshot` tool](../../core_concepts/22_ai_generation/index.mdx#app-preview-screenshots) (Google Chrome only), and you can drag screenshots into the chat as [image attachments](../../core_concepts/22_ai_generation/index.mdx#image-attachments). ## When to use the UI editor vs CLI | Scenario | Recommended | |----------|-------------| | Quick edits to an existing app | UI editor | | New app from scratch | CLI (`wmill app new`) | | Complex frontend with many dependencies | CLI (`wmill app dev`) | | Team collaboration with git | CLI + [git sync](../../advanced/11_git_sync/index.mdx) | --- ## Apps quickstart Source: https://www.windmill.dev/docs/getting_started/apps_quickstart # Apps quickstart (low-code, legacy) :::info Legacy The low-code app editor is legacy. For new apps, we recommend [full-code apps](../../full_code_apps/index.mdx) with React or Svelte. See the [full-code apps quickstart](../9_full_code_apps_quickstart/index.mdx) to get started. ::: Welcome to the low-code apps quickstart! This page will provide you with the necessary knowledge to build your first applications in a matter of minutes using the drag-and-drop app editor. If you're more into videos, you can check out our tutorial on the App editor: Although Windmill provides [auto-generated UIs to scripts and flows](../../core_concepts/6_auto_generated_uis/index.mdx), you can build your own internal applications designed to your needs. Either with the [low-code app editor](../../apps/0_app_editor/index.mdx), or by building [full-code apps](../../full_code_apps/index.mdx) with React or Svelte. Windmill applications are customized UIs to interact with datasources (web, internal, data providers, etc). They are a great way to have non-technical users interact with custom-made workflows. In short, what you need to remember about apps: - They work on a what-you-see-is-what-you-get basis. - You can connect apps and components to [datasources](../../integrations/0_integrations_on_windmill.mdx). - Components can be empowered by Windmill [scripts](../../getting_started/0_scripts_quickstart/index.mdx) and [flows](../6_flows_quickstart/index.mdx). :::tip Follow our [detailed section](../../apps/0_app_editor/index.mdx) on the App editor for more information. ::: To create your first app, you could pick one from our [Hub](https://hub.windmill.dev/apps) and fork it. Here, we're going to build our own app from scratch, step by step. From the Windmill home page, click **New** and select **App (low-code)**, and let's get started! ### Toolbar Before jumping in, you should decide if you want to build a Desktop or a Mobile app. Just click on the appropriated icon on top. Switch from Editor to Preview mode to take a step back on what you're building. ![Toolbar](./toolbar.png.webp) ### Components The App editor works on a drag-and-drop basis. From the right-side menu, click on a component to create it on the [canvas](../../apps/1_canvas.mdx), move it maintaining a click on it and delete it clicking on `Delete component` at the bottom of the `Settings` tab. You can resize your components by dragging the resize handler on the bottom-right corner. If you have numerous components you might need to Anchor some of them to have them unmovable by other components. Check out all of our components at [this page](../../apps/4_app_configuration_settings/1_app_component_library.mdx) or (better) directly from the app editor. Know that we can quickly add components to the library, just [reach out to us](../../misc/6_getting_help/index.mdx). ### Empower components with scripts The beauty of Windmill App editor is the integrations of scripts & workflows to components. On the bottom of the app editor, you can find the [Runnable editor](../../apps/3_app-runnable-panel.mdx). It allows you to create, edit or manage the scripts or flows linked to components (Runnables), and [background runnables](../../apps/3_app-runnable-panel.mdx#background-runnables). From a component, click on `Create an inline script` or `Select a script or flow` (from workspace or hub) and you're good! ![App Runnables panel](../../assets/apps/0_app_editor/app-sections.png) The [Outputs](../../apps/2_outputs.mdx) of each component can be found on the left side menu. Each of them is associated with a component (see the component id). ![App Outputs](../../assets/apps/0_app_editor/app-outputs.png.webp) [Inputs can be connected to any output](../../apps/2_connecting_components/index.mdx): on a component click on `Connect` and associate inputs to outputs. At any time, have a look at your app's Inputs associated with each component on the dedicated `App inputs` tab found in the `⋮` menu of the toolbar. ![Apps inputs](./apps_inputs.png.webp) ### Tailor the look of your app At the [component level](../../apps/4_app_configuration_settings/4_app_styling.mdx#component-level), on the `Settings` tab, configure the style of each component (color, size, label, etc.) either directly in the boxes or using Custom CSS (on the Component Settings - Styling tab). ![Customize components](./customize_component.png.webp) At the [app level](../../apps/4_app_configuration_settings/4_app_styling.mdx#global-styling), you can give a harmonized style to your app with CSS. On the `Global Styling` tab, give details in boxes or in JSON, for the whole app and per class of component. ![App styling](./customize_app.png.webp) ### Time to test While building your app, you can try each component clicking on refresh button. You can do a refresh all for the whole app with the refresh button on top of the canvas, or schedule automatic refresh. ![Refresh app](./refresh_app.png.webp) Most convenient solution to test your app might just be to [preview](../../apps/0_toolbar.mdx#preview-mode) it: ![App previewed](./app_previewed.png 'App previewed') For possible bugs, there is a `Debug runs` tab to review past runs with details for each component. ![Debug runs](./debug_runs.png.webp) ### Then what? Your app is automatically saved as a [draft](../../core_concepts/0_draft_and_deploy/index.mdx#draft) as you edit. When you're ready, [Deploy](../../core_concepts/0_draft_and_deploy/index.mdx#deployed-version) the current version to make it available to users with a proper and explicit name. All additional changes can be seen and reversed in the [Diff Viewer](../../apps/0_toolbar.mdx#diff). Once it's saved, it's ready to use! You can also `Publish` it, or even `Publish to Hub` and export it in JSON or Hub compatible JSON from the `⋮` tab. Follow our [detailed section](../../apps/0_app_editor/index.mdx) on the App editor for more information. --- ## Flows quickstart Source: https://www.windmill.dev/docs/getting_started/flows_quickstart # Flows quickstart The present document will introduce you to [Flows](../../flows/1_flow_editor.mdx) and how to build your first one. > [Here](https://hub.windmill.dev/flows/43/) is an example of a simple flow built with Windmill. Have in mind that in Windmill, Scripts are at the basis of Flows and Apps. To sum up roughly, workflows are state machines [represented as DAGs](../../flows/16_architecture.mdx) (Directed Acyclic Graphs) to compose scripts together. To learn more about scripts, check the [Script quickstart](../0_scripts_quickstart/index.mdx). You will not necessarily have to re-build each script as you can reuse them from your workspace or from the [Hub](https://hub.windmill.dev/). Those workflows can run for-loops, branches (parallelizable), suspend themselves until a timeout or receiving events such as webhooks or approvals. They can be scheduled very frequently and check for new external items to process (what we call "Trigger" script). The result of a flow is the result of the last step executed, unless [error](../../flows/8_error_handling.mdx) was returned before or [Early return](../../flows/19_early_return.mdx) is set. The overhead and coldstart between each step is about 20ms, which is [faster than any other orchestration engine](/blog/launch-week-1/fastest-workflow-engine), by a large margin. To create your first workflow, you could also pick one from our [Hub](https://hub.windmill.dev/flows) and fork it. Here, we're going to build our own flow from scratch, step by step. From the [Windmill](../00_how_to_use_windmill/index.mdx) home page, click **New** and select **Flow**, and let's get started! :::tip Follow our [detailed section](../../flows/1_flow_editor.mdx) on the Flow editor for more information. ::: ## Settings ### Metadata The first thing you'll see is the [Settings](../../flows/3_editor_components.mdx#settings) menu. From there, you can set the [permissions](../../core_concepts/16_roles_and_permissions/index.mdx) of the workflow: User (by default, you), and [Folder](../../core_concepts/8_groups_and_folders/index.mdx) (referring to read and/or write groups). Also, you can give succinctly a Name, a Summary and a Description to your flow. Those are supposed to be explicit, we recommend you to give context and make them as self-explanatory as possible. ![Flows metadata](./flows_metadata.png.webp) ### Schedule On another tab, you can configure a [Schedule](../../core_concepts/1_scheduling/index.mdx) to trigger your flow. Flows can be [triggered](../../triggers/index.mdx) by any schedules, their [webhooks](../../core_concepts/4_webhooks/index.mdx) or their UI but they only have only one primary schedule with which they share the same path. This menu is where you set the primary schedule with CRON. The default schedule is none. ![Flows schedule](./flows_schedule.png.webp) ### Shared directory Last tab of the settings menu is the [Shared Directory](../../core_concepts/11_persistent_storage/states_resources_shared_directory.mdx#shared-directory). By default, flows on Windmill are based on a [result basis](#how-data-is-exchanged-between-steps). A step will take as inputs the results of previous steps. And this works fine for lightweight automation. For heavier ETLs and any output that is not suitable for JSON, you might want to use the `Shared Directory` to share data between steps. Steps share a folder at `./shared` in which they can store heavier data and pass them to the next step. Get more details on the [Persistent storage & databases dedicated page](../../core_concepts/11_persistent_storage/index.mdx). ![Flows shared directory](./flows_shared_directory.png.webp) ### Worker group When a [worker group](../../core_concepts/9_worker_groups/index.mdx) is defined at the flow level, any steps inside the flow will run on that worker group, regardless of the steps' worker group. If no worker group is defined, the flow controls will be executed by the default worker group 'flow' and the steps will be executed in their respective worker group. You can always go back to this menu by clicking on `Settings` on the top lef, or on the name of the flow on the [toolbar](../../flows/3_editor_components.mdx#toolbar). ## How data is exchanged between steps Flows on Windmill are generic and reusable, they therefore expose inputs. Input and outputs are piped together. Inputs are either: - Static: fixed values set directly in the step input fields (strings, numbers, JSON, etc.). These are constants that do not change between executions. - [Flow env variables](../../flows/3_editor_components.mdx#flow-env-variables): flow-level constants accessible from any step using `flow_env.VARIABLE_NAME`. They support strings, JSON and [resources](../../core_concepts/3_resources_and_types/index.mdx). - [Dynamically linked to others](../../flows/16_architecture.mdx): with [JSON objects](../../core_concepts/13_json_schema_and_parsing/index.mdx) as result that allow to refer to the output of any step. You can refer to the result of any step: - using the id associated with the step - clicking on the plug logo that will let you pick flow inputs or previous steps' results (after testing flow or step). ## Flow editor On the left of the editor, you'll find a graphical view of the flow. From there you can architecture your flow and take action at each step. ![Flow editor menu](./flow_editor_menu.png.webp) :::tip Pro tips Keep your flows organized and documented with [sticky notes](../../flows/24_sticky_notes.mdx) for free-floating comments and TODOs, and with [flow groups](../../flows/1_flow_editor.mdx#flow-groups) to visually cluster related steps and document complex workflow sections. ::: There are five kinds of scripts: [Action](../../flows/3_editor_components.mdx#flow-actions), [Trigger](../../flows/10_flow_trigger.mdx), [Approval](../../flows/11_flow_approval.mdx), [Error handler](../../flows/7_flow_error_handler.md) and [Preprocessor](../../core_concepts/43_preprocessors/index.mdx). You can sequence them how you want. Action is the default script type. Each script can be called from Workspace or [Hub](https://hub.windmill.dev/), you can also decide to write them inline. ![Import or write scripts](./import_or_write_scripts.png.webp) Your flow can be deepened with [additional features](../../flows/1_flow_editor.mdx), below are some major ones. ### For loops [For loops](../../flows/12_flow_loops.md) are a special type of steps that allows you to iterate over a list of items, given by an iterator expression. ![Flows For loops](./for_loops.png.webp) ### While loops While loops execute a sequence of code indefinitely until the user cancels or a step set to [Early stop](../../flows/2_early_stop.md) stops. ### Branching [Branches](../../flows/13_flow_branches.md) build branching logic to create and manage complex workflows based on conditions. There are two of them: - [Branch one](../../flows/13_flow_branches.md#branch-one): allows you to execute a branch if a condition is true. - [Branch all](../../flows/13_flow_branches.md#branch-all): allows you to execute all the branches in parallel, as if each branch is a flow. ![Flow branching](flow_branches.png.webp) ### Retries At each step, Windmill allows you to [customize the number of retries](../../flows/14_retries.md) by going on the `Advanced` tabs of the individual script. If defined, upon error this step will be retried with a delay and a maximum number of attempts. ![Flows retries](./flows_retries.png.webp) ### Suspend/Approval Step At each step you can add [Approval scripts](../../flows/11_flow_approval.mdx) to manage security and control over your flows. Request approvals can be sent by email, Slack, anything. Then you can automatically resume workflows with secret webhooks after the approval steps. ![Approval step diagram](../../assets/flows/approval_diagram.png 'Approval step diagram') You can find all the flows' features in their [dedicated section](../../flows/1_flow_editor.mdx). ## Triggers There are several ways to trigger a flow with Windmill. 1. The most direct one is from the [autogenerated UI provided by Windmill](../../core_concepts/6_auto_generated_uis/index.mdx). It is the one you will see from the flow editor. 2. A similar but more customized way is to use Windmill Apps using the [App editor](../7_apps_quickstart/index.mdx). 3. We saw above that you can trigger flows using [schedules](../../core_concepts/1_scheduling/index.mdx) that you can check from the [Runs](../../core_concepts/5_monitor_past_and_future_runs/index.mdx) page. One special way to use scheduling is to combine it with [trigger scripts](../../flows/10_flow_trigger.mdx). 4. [Execute flows from the CLI](../../advanced/3_cli/index.mdx) to trigger your flows from your terminal. 5. [Trigger the flow from another flow](../../triggers/index.mdx#trigger-from-flows). 6. Using [trigger scripts](../../flows/10_flow_trigger.mdx) to trigger only if a condition has been met. 7. [Webhooks](../../core_concepts/4_webhooks/index.mdx). Each Flow created in the app gets autogenerated webhooks. You can see them once you flow is saved. You can even [trigger flows without leaving Slack](/blog/handler-slack-commands)! You can test your triggers in test mode: ## Test your flow You don't have to explore all Flow editor possibilities at once. At each step, test what you're building to keep control on your wonder. You can also test up to a certain step by clicking on an action (x) and then on `Test up to x`. When you're done, [deploy](../../core_concepts/0_draft_and_deploy/index.mdx) your flow, schedule it, [create and app from it](../../core_concepts/6_auto_generated_uis/index.mdx), or even [publish it to Hub](../../misc/1_share_on_hub/index.md). Follow our [detailed section](../../flows/1_flow_editor.mdx) on the Flow editor for more information. ## Flow as Code Flows are not the only way to write distributed programs that execute distinct jobs. Another approach is to write a program that defines the jobs and their dependencies, and then execute that program within a [Python](../0_scripts_quickstart/2_python_quickstart/index.mdx) or [TypeScript](../0_scripts_quickstart/1_typescript_quickstart/index.mdx) script. This is known as workflows as code. ![Flow as code](../../core_concepts/31_workflows_as_code/wac-editor-1.png) --- ## Full code apps quickstart Source: https://www.windmill.dev/docs/getting_started/full_code_apps_quickstart # Full-code apps quickstart This guide walks you through building your first [full-code app](../../full_code_apps/index.mdx). We'll create a React app from the Windmill UI, explore the scaffolded code, add a second backend runnable, and wire both into a polished frontend. ![Full code apps](./full_code_app_demo.png 'Full code apps') Full-code apps give you complete control over your UI with React or Svelte, while Windmill handles backend execution, permissions and deployment. | | Full-code apps | Low-code apps | |---|---|---| | **UI** | Custom React/Svelte components | Drag-and-drop component library | | **Frontend logic** | Full framework features (hooks, stores, routing) | Connecting components + inline scripts | | **Backend** | Scripts in `backend/` folder, any language | Runnables panel, inline or workspace scripts | | **Local dev** | `wmill app dev` with hot reload | Web-based editor only | | **Best for** | Custom UIs, complex interactions, existing codebases | Quick dashboards, forms, CRUD interfaces | ## Step 1: Create the app ### From the platform From your [Windmill](../00_how_to_use_windmill/index.mdx) home page, click **New** and select **App (full-code)**. ![Pick full-code app](./pick_raw_app.png 'Pick full-code app') In the setup dialog: 1. Pick a framework (React or Svelte 5), for the example we'll use React 19 2. Choose a Data configuration. Here we'll use a new datatable. It's not required for apps to have a datatable, it will be used only in a [dedicated section](#step-5-use-a-data-table-for-persistence) of the quickstart. 3. Start the app 'without AI'. Or just enter a prompt and start with AI, and you're done for the quickstart :) ![Pick React framework](./pick_react.png 'Pick React framework') The [UI editor](../../full_code_apps/6_ui_editor/index.mdx) opens with a scaffolded project. ### From the CLI You can do the same from the [Windmill CLI](../../advanced/3_cli/index.mdx): ```bash wmill app new ``` The wizard prompts for the same choices. Then install dependencies and start the dev server: ```bash cd f/folder/my_app.raw_app npm install wmill app dev ``` This starts a local server with hot reload at `http://localhost:4000`. ## Step 2: Explore the scaffolded project The created app has this structure: ``` f/folder/my_app.raw_app/ ├── raw_app.yaml # App metadata and configuration ├── package.json # Frontend dependencies ├── index.tsx # Entry point (renders App) ├── App.tsx # Main React component ├── index.css # Styles └── backend/ ├── a.yaml # Sample backend runnable config └── a.ts # Sample backend runnable code ``` ![Default App.tsx](./default_tsx.png 'Default App.tsx') ### The default App.tsx The scaffolded `App.tsx` looks like this: ```tsx import './index.css' const App = () => { const [value, setValue] = useState(undefined as string | undefined) const [loading, setLoading] = useState(false) async function runA() { setLoading(true) try { setValue(await backend.a({ x: 42 })) } catch (e) { console.error(e) } setLoading(false) } return

hello world

{loading ? 'Loading ...' : value ?? 'Click button to see value here'}
} export default App ``` It imports `backend` from `./wmill` - this is an auto-generated module that provides typed functions to call your [backend runnables](../../full_code_apps/2_backend_runnables/index.mdx). Here, clicking the button calls `backend.a()` which runs the sample runnable `a` in the `backend/` folder. ### The default backend runnable The sample runnable `backend/a.ts` is a simple TypeScript function: ```typescript // import * as wmill from "windmill-client" export async function main(x: string) { return x } ``` You can preview your UI by selecting 'App.tsx' to see it in the right pane, or by clicking 'Preview' in the UI editor for a fullscreen view. If you're using the CLI, open `http://localhost:4000` in your browser to access the app. When you click the button, it sends a request to the backend and displays the returned result. ![Default backend runnable](./default_backend.png 'Default backend runnable') ### How it works The key concept: `backend.a({ x: 42 })` sends the call to a Windmill worker that executes `backend/a.ts` and returns the result. Your frontend never runs the backend code directly - it goes through Windmill's execution engine via WebSocket, which means you get logging, permissions and error handling for free. ## Step 3: Edit and add backend runnables The scaffolded app comes with one runnable (`a`). Let's update it and add a second one. ### From the UI editor Click on the `a` runnable in the runnables panel. Give it the summary "Multiply" and replace the code with: ```typescript // backend/a.ts return `Result: ${x} × 2 = ${x * 2}`; } ``` ![Multiply runnable](./multiply.png 'Multiply runnable') Now add a second runnable - this time in [Python](../../getting_started/0_scripts_quickstart/2_python_quickstart/index.mdx) as you can mix languages within the same app: 1. In the runnables panel on the right, click the `+` button 2. Select **Python** as the language 3. Name it `b` and give it the summary "Get timestamp" ![Choose language](./choose_language.png 'Choose language') Paste this code: ```python # backend/b.py from datetime import datetime def main(format: str): now = datetime.now() if format == "iso": return now.isoformat() elif format == "locale": return now.strftime("%c") else: return str(now) ``` ![Get timestamp runnable](./get_timestamp.png 'Get timestamp runnable') As you can see, the [auto-generated UI](../../core_concepts/6_auto_generated_uis/index.mdx) updated with the new input name (`format`). You now have two runnables in different languages: `a` (TypeScript) doubles a number, `b` (Python) returns a formatted date. The frontend calls them the exact same way - it doesn't need to know which language runs behind the scenes. ### From local files Alternatively, work directly in `backend/`: - Edit `backend/a.ts` with the multiply code above - Create `backend/b.py` with the Python code above The language is auto-detected from the file extension (`.ts` for TypeScript, `.py` for Python) and the runnable ID is derived from the filename (`a`, `b`). ## Step 4: Build the frontend Now let's update `App.tsx` to call both runnables. The auto-generated `wmill` module automatically picks up the new `b` runnable, so we can call `backend.a()` and `backend.b()` right away. Replace the content of `App.tsx` with: ```tsx import './index.css' const App = () => { const [valueA, setValueA] = useState(undefined) const [valueB, setValueB] = useState(undefined) const [loadingA, setLoadingA] = useState(false) const [loadingB, setLoadingB] = useState(false) const [inputNumber, setInputNumber] = useState(42) async function runA() { setLoadingA(true) try { setValueA(await backend.a({ x: inputNumber })) } catch (e) { console.error('Error running a:', e) } setLoadingA(false) } async function runB() { setLoadingB(true) try { setValueB(await backend.b({ format: 'locale' })) } catch (e) { console.error('Error running b:', e) } setLoadingB(false) } async function runBoth() { setLoadingA(true) setLoadingB(true) try { const [resultA, resultB] = await Promise.all([ backend.a({ x: inputNumber }), backend.b({ format: 'iso' }) ]) setValueA(resultA) setValueB(resultB) } catch (e) { console.error('Error running both:', e) } setLoadingA(false) setLoadingB(false) } return (

Full-code app demo

Calling 2 backend runnables

Multiply (TypeScript)

{loadingA ? 'Loading...' : valueA ?? 'Click a button to see result'}

Timestamp (Python)

{loadingB ? 'Loading...' : valueB ?? 'Click a button to see result'}
) } export default App ``` A few things to notice: - Each button calls a different backend runnable (`backend.a()` or `backend.b()`) - **Run both** uses `Promise.all` to call both runnables in parallel - each one runs as a separate Windmill job - The `format` parameter on `backend.b()` is passed as an argument, just like `x` on `backend.a()` - `a` runs TypeScript on a Bun worker, `b` runs Python - the frontend doesn't need to care ### Update the styles Replace `index.css` to give the app a cleaner look: ```css .container { max-width: 600px; margin: 0 auto; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } h1 { margin-bottom: 5px; color: #333; } .subtitle { color: #666; margin-top: 0; margin-bottom: 24px; } .input-section { margin-bottom: 20px; } .input-section label { display: flex; align-items: center; gap: 10px; font-weight: 500; } .input-section input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; width: 100px; } .buttons { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 24px; } button { padding: 10px 18px; border: 1px solid #ddd; border-radius: 6px; background: white; cursor: pointer; font-size: 14px; transition: all 0.2s; } button:hover:not(:disabled) { background: #f5f5f5; border-color: #ccc; } button:disabled { opacity: 0.6; cursor: not-allowed; } button.primary { background: #3b82f6; color: white; border-color: #3b82f6; } button.primary:hover:not(:disabled) { background: #2563eb; } .results { display: grid; gap: 16px; } .result-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; background: #fafafa; } .result-card h3 { margin: 0 0 10px 0; font-size: 14px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; } .result-value { font-size: 16px; color: #333; font-family: 'Monaco', 'Menlo', monospace; word-break: break-all; } ``` See the preview in the right part of the screen of App.tsx (or click 'Preview' in the UI editor to see it in full screen, or check `http://localhost:4000` if using the CLI) to see the result. Try clicking each button individually, then "Run both" to see parallel execution. ![Updated App.tsx](./updated_tsx.png 'Updated App.tsx') ![Index CSS](./index_css.png 'Index CSS') :::info Svelte If you chose Svelte 5 instead of React, the same pattern applies. Import `backend` from `./wmill` and use Svelte's `$state` and `$effect` for reactivity. See the [frontend reference](../../full_code_apps/3_frontend/index.mdx) for Svelte examples. ::: ### How backend calls work Every call to `backend.a()` or `backend.b()` is a real Windmill job execution: - `backend.xxx(args)` - calls a runnable and waits for the result (synchronous) - `backendAsync.xxx(args)` - starts a runnable and returns a job ID immediately (for long-running tasks) - `waitJob(jobId)` - waits for an async job to complete ## Step 5: Use a data table for persistence So far our runnables compute values on the fly. Full-code apps can also read and write to Windmill [data tables](../../core_concepts/11_persistent_storage/data_tables.mdx) - a built-in storage layer. ### Set up a database First, make sure a database is configured in your workspace. Go to **Workspace settings** > **Data Tables** and set up a database connection (or use the [Custom instance database](../../core_concepts/53_custom_instance_database/index.mdx) if available). ![Create table](./create_table.png 'Create table') ### Add a table to your app In the raw app editor, open the **Data** section on the left panel and click the `+` button. You can either pick an existing table (public or from other apps) or create a new one for your app. Let's create a new table: 1. Click `+` in the Data section 2. Select **Create new table** 3. Name it `computation_logs` 4. Define the columns: - `id` — `BIGSERIAL` (primary key, added by default) - `input` — `INT` - `result` — `TEXT` - `created_at` — `TIMESTAMP`, default `now()` 5. Click **Create table** ![Create table columns](./create_table_2.png 'Create table columns') The table is now whitelisted for your app. You can view its schema and data from the Data section. ### Add a SQL runnable Now create a backend runnable that queries this table. In the runnables panel, click `+` and select **PostgreSQL** as the language. Name it `get_logs`. For the database resource, pick the same resource as the one configured in your workspace Data Tables settings. ```sql -- backend/get_logs.pg.sql SELECT * FROM app_demo.computation_logs ORDER BY created_at DESC LIMIT 10; ``` ![Get logs runnable](./get_logs.png 'Get logs runnable') From the frontend, call it like any other runnable: ```typescript const logs = await backend.get_logs(); ``` The frontend code doesn't need to know whether a runnable is TypeScript, Python or SQL - the `wmill` module handles them all the same way. :::tip CLI From local files, the `.pg.sql` extension tells Windmill to run the script as a PostgreSQL query. Other SQL dialects are supported too (`.my.sql` for MySQL, `.bq.sql` for BigQuery, etc.). See the [backend runnables](../../full_code_apps/2_backend_runnables/index.mdx) reference for the full list. ::: ## Step 6: Deploy ### From the UI editor Click the **Deploy** button in the toolbar. Each deployment creates a new version of your app. ![Deploy](./deploy.png 'Deploy') ![Deployed app](./deployed_app.png 'Deployed app') ### From the CLI Generate lock files for your runnables and push: ```bash wmill generate-metadata wmill sync push ``` ### Make it public To make the app accessible without login, add `public: true` to `raw_app.yaml`: ```yaml summary: "Full-code app demo" public: true ``` Admins can also set a custom URL path: ```yaml custom_path: "my-demo" ``` The app is then accessible at `https:///apps/custom/my-demo`. ## Runnable configuration So far we've used code-only runnables (just a file in `backend/`). For more control, you can add a `.yaml` config file alongside the code to pre-fill inputs: ```yaml # backend/a.yaml type: inline fields: x: type: static value: 100 ``` This pre-fills the `x` parameter so the frontend doesn't need to pass it. You can also reference existing workspace [scripts](../../script_editor/index.mdx) or [flows](../../flows/1_flow_editor.mdx) instead of writing inline code: ```yaml # backend/send_notification.yaml type: script path: f/production/send_slack_notification ``` ## Next steps You now have a working full-code app with a custom React frontend calling multiple backend runnables. From here you can: - Add [backend runnables](../../full_code_apps/2_backend_runnables/index.mdx) in any language (Python, SQL, Go, etc.) - Style your app with CSS, Tailwind or any React library - Set up [CI/CD with git sync](../../advanced/11_git_sync/index.mdx) for team workflows - Use [Windmill AI](../../core_concepts/22_ai_generation/index.mdx) to generate apps from prompts --- ## How to use windmill Source: https://www.windmill.dev/docs/getting_started/how_to_use_windmill # Getting started with Windmill Windmill is a fast, **open-source** workflow engine and developer platform to build endpoints, workflows and UIs, coding in TypeScript, Python, Go, and [many other languages](../0_scripts_quickstart/index.mdx). For the full pitch and how Windmill compares to its alternatives, see [What is Windmill?](../../intro.mdx). ## Choose your setup ### Windmill cloud Quickly get started with our [Cloud App](https://app.windmill.dev/), no credit card required. Sign up using GitHub, GitLab, Google, or Microsoft SSO. Start with 1,000 monthly executions on our Community Plan, and easily upgrade for more. [Start with Windmill cloud](https://app.windmill.dev/). Windmill cloud is hosted in the US. We offer dedicated cloud instances in the EU for [Cloud Enterprise](/pricing) customers. ### Self-host Windmill For full control over your infrastructure, self-host Windmill using our [helm charts](https://github.com/windmill-labs/windmill-helm-charts) for Kubernetes or docker-compose for simpler setups. [Learn how to self-host Windmill](../../advanced/1_self_host/index.mdx). ## Development options ### Integrated Development Environment (IDE) Windmill supports development directly within its [built-in IDE](../../code_editor/index.mdx), tailored for creating scripts, workflows, [low-code apps](../../apps/0_app_editor/index.mdx) and [full-code apps](../../full_code_apps/index.mdx) efficiently. ### Local development Prefer your own setup? No problem. Windmill integrates smoothly with local environments, including a [VS Code extension](../../cli_local_dev/1_vscode-extension/index.mdx) and tools for [Git-based deployment to production](../../advanced/12_deploy_to_prod/index.mdx). [Explore local development options](../../advanced/4_local_development/index.mdx). ### Ready to deploy? Move from staging to production seamlessly with Windmill's deployment guides, ensuring your projects are production-ready. [Deploy to production](../../advanced/12_deploy_to_prod/index.mdx). ## Creating resources from the home page The workspace home page lists everything in your workspace. To create something new, use the **New** button in the top-right corner of the header. Hovering (or clicking) it opens a two-pane popover: the option list on the right, a description of the highlighted option on the left. Clicking **New** directly creates a script, the default option. The available options are: - [Script](../0_scripts_quickstart/index.mdx) - a single standalone script in [any supported language](../0_scripts_quickstart/index.mdx). - [Flow](../../flows/1_flow_editor.mdx) - compose scripts into a workflow with branches, loops, approvals and retries. - [App (full-code)](../9_full_code_apps_quickstart/index.mdx) - build a UI with React or Svelte. - [Workflow-as-Code](../../core_concepts/31_workflows_as_code/index.mdx) - badged _Advanced_. Express a whole workflow as a single script; picking it offers a **Python** or **TypeScript** choice. - [Data pipelines](../10_pipeline_quickstart/index.mdx) - badged _Alpha_. Visual editor to chain ingestion, transformation and materialization steps. - [App (low-code)](../7_apps_quickstart/index.mdx) - badged _Legacy_. Drag-and-drop UI builder. For new apps, prefer full-code apps. Some options also expose secondary import actions in their detail panel (for example _Import flow_, _Import Workflow-as-Code_, _Import full-code app_, _Import low-code app_), which open a drawer where you paste a YAML or JSON export to pre-fill the editor. The header also has a **Hub** button that opens the [Windmill Hub](https://hub.windmill.dev/) in a new tab, and a **CLI / MCP** button to connect external tooling. --- ## Pipeline quickstart Source: https://www.windmill.dev/docs/getting_started/pipeline_quickstart # Pipelines quickstart :::caution Alpha Pipelines are in alpha. The entry point is deliberately tucked away while we develop the feature, and the annotation syntax and behavior described in this guide are still evolving and may change in future releases. We would love your feedback - share it on [Discord](https://discord.com/invite/V7PM2YHsPB) or [GitHub](https://github.com/windmill-labs/windmill). ::: This guide builds your first [pipeline](../../core_concepts/63_pipelines/index.mdx): a set of scripts in a folder wired together automatically by the data they read and write, no manual orchestration. It uses the recommended [DuckDB](../../integrations/duckdb.md) + [DuckLake](../../core_concepts/11_persistent_storage/ducklake.mdx) path, where each step materializes a table (this is what makes the writes idempotent, versioned and testable). It takes about five minutes. DuckLake is not required, though. Pipelines wire scripts in any language (Python, TypeScript, ...) by the plain [assets](../../core_concepts/52_assets/index.mdx) they exchange - S3 objects, resources, data tables or volumes - with the same `-- on` model and no materialization. See [inputs and outputs](../../core_concepts/63_pipelines/index.mdx#inputs-and-outputs-assets) for that path; this guide shows DuckLake because it is the most powerful default. Already using dbt? You do not have to port the project into a pipeline. Windmill runs an unmodified dbt project as [a script of its own kind](../0_scripts_quickstart/16_dbt_quickstart/index.mdx), with dbt still owning its models, refs and tests, and shows its models in the asset graph as `dbt://` nodes beside whatever pipelines you build. Windmill's own pipelines page (`/pipeline`) points there too, next to the [warehouse settings](../0_scripts_quickstart/16_dbt_quickstart/index.mdx#configure-a-warehouse) a dbt project needs. :::info Prerequisite Because this guide uses DuckLake, you need a [workspace storage and a DuckLake](../../core_concepts/11_persistent_storage/ducklake.mdx) configured. The default DuckLake is named `main`; this guide uses it. (A plain S3-based pipeline needs only the [workspace storage](../../core_concepts/38_object_storage_in_windmill/index.mdx#workspace-object-storage).) On a fresh workspace the pipelines page shows a **setup checklist** whenever either prerequisite is missing, with a link straight to the [workspace object storage](../../core_concepts/38_object_storage_in_windmill/index.mdx#workspace-object-storage) and [DuckLake](../../core_concepts/11_persistent_storage/ducklake.mdx) settings that fix it. Nothing materializes until both are set. ::: ## 1. Create a pipeline On the home page, click **New** and select **Data pipelines** (badged _Alpha_) in the popover. ![Create a pipeline from the New popover](./create_pipeline_menu.png 'Create a pipeline from the New popover') You can also open the pipelines index page at `/pipeline`: it lists existing pipelines with their script counts and lets you pick or create a folder. When you edit a SQL or DuckDB script that is not yet part of a pipeline, a dismissible hint below the toolbar links there too. ![The pipelines index page](./pipeline_index_page.png 'The pipelines index page') Pipelines live in a [folder](../../core_concepts/8_groups_and_folders/index.mdx) (for example `f/demo`); every script you add to it and mark with `-- pipeline` becomes part of the same pipeline graph. ## 2. Add a producer script Create a DuckDB script `f/demo/ingest`. The `-- pipeline` line places it in the folder's pipeline, and `-- materialize` tells Windmill to own the write: it creates the `ducklake://main/events` table from the trailing `SELECT` and records a snapshot and row count. ```sql -- pipeline -- materialize ducklake://main/events SELECT * FROM (VALUES (1, 'click', TIMESTAMP '2026-01-01 10:00'), (2, 'view', TIMESTAMP '2026-01-01 10:05'), (3, 'click', TIMESTAMP '2026-01-01 11:00') ) AS t(id, kind, ts); ``` ## 3. Add a consumer script Create a DuckDB script `f/demo/rollup`. The `-- on ducklake://main/events` annotation declares the table it reads, which becomes an incoming edge: this script runs automatically whenever `events` is materialized. It materializes its own aggregate table. ```sql -- pipeline -- on ducklake://main/events -- materialize ducklake://main/events_by_kind ATTACH 'ducklake://main' AS dl; SELECT kind, count(*) AS n FROM dl.events GROUP BY kind; ``` ## 4. Open the pipeline graph Open the folder and select the pipeline view. You will see the lineage: `ingest` → `ducklake://main/events` → `rollup` → `ducklake://main/events_by_kind` ## 5. Run it On the `ingest` node, choose "Run + downstream". `ingest` materializes `events`, and the asset cascade automatically fires `rollup`, which materializes `events_by_kind`. Each node shows live status, its DuckLake snapshot and row count as the run progresses, and the result panel previews the materialized table. That is the whole model: mark scripts with `-- pipeline`, declare inputs with `-- on`, and Windmill infers and runs the graph from asset lineage. Adding `-- materialize` (the optional DuckLake layer used here) is what also gives you idempotent re-runs, time-travel, [data tests](../../core_concepts/63_pipelines/materialization.mdx#data-tests) and [backfill](../../core_concepts/63_pipelines/materialization.mdx#partition-status-and-backfill) with no extra work. ## Next steps Add materialization strategies (merge, append, SCD2 history), partitions, schedules, AND/OR joins, data tests and debounce. Every annotation and option is documented, with examples, on the concept page. --- ## Scripts quickstart Source: https://www.windmill.dev/docs/getting_started/scripts_quickstart # Scripts quickstart Windmill supports scripts in TypeScript, Python, Go, PHP, Bash, C#, SQL and more. --- ## Ansible quickstart Source: https://www.windmill.dev/docs/getting_started/scripts_quickstart/ansible # Ansible quickstart In this quickstart guide, we will write our first script/playbook with [Ansible](https://www.ansible.com/). This tutorial covers how to create a simple Ansible script through Windmill web IDE. See the dedicated page to [develop scripts locally](../../../advanced/4_local_development/index.mdx). Scripts are the basic building blocks in Windmill. They can be [run and scheduled](../../../triggers/index.mdx) as standalone, chained together to create [Flows](../../../flows/1_flow_editor.mdx) or displayed with a personalized user interface as [Apps](../../7_apps_quickstart/index.mdx). Scripts consist of 2 parts: - [Code](#code-playbook): for Ansible this is a playbook file written in yaml. - [Settings](#settings): settings & metadata about the Script such as its path, summary, description, [JSON Schema](../../../core_concepts/13_json_schema_and_parsing/index.mdx) of its inputs (inferred from its signature). When stored in a code repository, these 2 parts are stored separately at `.playbook.yml` and `.script.yaml` ![Ansible in Windmill](./create_ansible_script.png) ## Settings ![New script](../../../../static/images/script_languages.png "New script") As part of the [settings](../../../script_editor/settings.mdx) menu, each script has metadata associated with it, enabling it to be defined and configured in depth. - **Summary** (optional) is a short, human-readable summary of the Script. It will be displayed as a title across Windmill. If omitted, the UI will use the `path` by default. - **Path** is the Script's unique identifier that consists of the [script's owner](../../../core_concepts/16_roles_and_permissions/index.mdx), and the script's name. The owner can be either a user, or a group ([folder](../../../core_concepts/8_groups_and_folders/index.mdx#folders)). - **Description** is where you can give instructions through the [auto-generated UI](../../../core_concepts/6_auto_generated_uis/index.mdx) to users on how to run your Script. It supports markdown. - **Language** of the script. - **Script kind**: Action (by default), [Trigger](../../../flows/10_flow_trigger.mdx), [Approval](../../../flows/11_flow_approval.mdx), [Error handler](../../../flows/7_flow_error_handler.md) or [Preprocessor](../../../core_concepts/43_preprocessors/index.mdx). This acts as a tag to filter appropriate scripts from the [flow editor](../../6_flows_quickstart/index.mdx). This menu also has additional settings on [Runtime](../../../script_editor/settings.mdx#runtime), [Generated UI](#generated-ui) and [Triggers](../../../script_editor/settings.mdx#triggers). Now click on the code editor on the left side. ## Code (Playbook) In order to make Ansible playbooks compatible with the Windmill environment and script model, there is some extra information preceding the start of the playbook that can be entered. Because of this, an Ansible playbook in Windmill will typically look like this: ![Ansible in Windmill](./ansible_script_ide.png) ```yml --- inventory: - resource_type: ansible_inventory # You can pin an inventory to this script: # resource: u/user/your_resource # Additional inventories available as script arguments additional_inventories: - "delegate_git_repository/hosts/inventory.ini" # File resources will be written in the relative \`target\` location before # running the playbook files: - resource: u/user/fabulous_jinja_template target: ./config_template.j2 # Define the arguments of the Windmill script extra_vars: world_qualifier: type: string dependencies: galaxy: collections: - name: community.general - name: community.vmware python: - jmespath --- - name: Echo hosts: 127.0.0.1 connection: local vars: my_result: a: 2 b: true c: "Hello" tasks: - name: Print debug message debug: msg: "Hello, {{world_qualifier}} world!" - name: Write variable my_result to result.json delegate_to: localhost copy: content: "{{ my_result | to_json }}" dest: result.json ``` There are two YAML documents in series, the second being the Ansible playbook. The first one is only used by Windmill, and will not be visible to Ansible when executing the playbook. It contains different sections that declare some metadata about the script. We will now go through each of these sections. ### Arguments (extra-args) Windmill scripts can take [arguments](../../../core_concepts/13_json_schema_and_parsing/index.mdx), and in order to define the names and types of the arguments you can use this section. These definitions will be parsed allowing the frontend to interactively display dynamic inputs for the script. ```yaml extra_vars: world_qualifier: type: string nested_object: type: object properties: a: type: string b: type: number some_arr: type: array objects: type: string ``` ![Parsing Yaml and generating UI](./extra_vars_ui.png) The type definition is inspired and tries to follow the [OpenAPI Data Types standard](https://swagger.io/docs/specification/data-models/data-types/). Note that not all features / types are supported, the best way to know what is supported is to test it out in the Web IDE. :::tip Argument defaults You can set a default value for your arguments by using a `default:` field, for example: ```yml extra_vars: my_string: type: string default: 'Fascinating String of Words' ``` ::: To use Windmill [resources](../../../core_concepts/3_resources_and_types/index.mdx) as types you can use the following type definition: ```yaml extra_vars: my_resource: type: windmill_resource resource_type: postgresql ``` ![Postgres Resource UI](./postgres_ui.png) Under the hood, Windmill will pass these variables using the `--extra-vars` flag to Ansible, so you can expect the according behavior. ### Static resources and variables Resources and [variables](../../../core_concepts/2_variables_and_secrets/index.mdx) that are hardcoded to a particular script can also be defined in the `extra-vars` section. This is because they are similarly passed through the `--extra-vars` flag in the Ansible playbook. This is what the syntax looks like: ```yml extra_vars: my_variable: type: windmill_variable variable: u/user/my_variable my_resource: type: windmill_resource resource: u/user/my_resource ``` Under `resource` or `variable` you can statically link the path to the resource/variable. As you do, you will notice the UI update and hide the resource input as it is now static. :::tip About static and non-static variables Note that Variables defined this way can only be static. If you want to use non-static Variables, define a normal argument with `type: string` and from the UI fill it with one of your Variables or Secrets. ::: ### Return values In Windmill scripts usually have a return value, which allows scripts to be chained in flows and run conditionally on the result of a previous operation. For Ansible playbooks you can achieve the same result by having one of the tasks (preferably the last one for coherence of results/errors) write a file named `result.json` with the JSON object you want to return: ```yaml --- tasks: [...] - name: Write variable my_result to result.json delegate_to: localhost copy: content: "{{ my_result | to_json }}" dest: result.json ``` Note that valid json must be written to the file or else the job will fail. Also, this should be done by the control node i.e. your worker, so it's important to use the `delegate_to: localhost` directive. ### Inventories When using ansbile playbooks, you would usually run a command such as `ansible-playbook playbook.yml -i inventory.ini`. The ways to pass inventories to Ansible in Windmill is by filling the following section: ```yaml inventory: - resource_type: ansible_inventory ``` To create similar resource type, refer to [creating plain text resources](../../../core_concepts/3_resources_and_types/index.mdx#plain-text-file-resources). Otherwise `ansible_inventory` should be available after syncing resource types from the hub. After adding this in the Web IDE, you will see a new `inventory.ini` argument pop up. You can then select or create a new ansible_inventory resource. ![inventory ui](./inventory_ui.png) If you don't want one of the inputs of the script be the inventory, you can pin a specific resource to the script by specifying its path. In this case you don't need to specify the resource_type anymore: ```yaml inventory: - resource: u/user/my_ansible_inventory ``` Then the UI will not prompt you for the inventory but will use this resource at every run of the script. If otherwise you wish to not specify any inventory, you can remove the section altogether By default, the inventory will be named `inventory.ini`, but if your inventory needs to have a different extension (e.g. dynamic invetories) you can specify the name of the inventory file like this: ```yaml inventory: - resource_type: c_dynamic_ansible_inventory name: hcloud.yml ``` Additionally, if you need to pass multiple inventories, you just need to continue the yaml array with your other invetories, they will all be passed to the `ansible-playbook` command. ```yaml # Declaring three different inventories to be passed to the playbook inventory: - resource: u/user/my_base_inventory name: base.ini - resource_type: ansible_inventory - resource_type: c_dynamic_ansible_inventory name: hcloud.yml ``` ### Additional inventories You can also declare additional inventories that will be made available as script arguments without specifying their source. This allows users to dynamically select inventories when running the script. ```yaml additional_inventories: - name: "Extra inventories" options: - "delegate_git_repository/hosts/inventory1.ini" - "delegate_git_repository/hosts/inventory2.ini" - "delegate_git_repository/hosts/inventory3.ini" ``` They can also be defined statically to always be passed in for this script: ```yaml additional_inventories: - "delegate_git_repository/hosts/permanent_inventory.ini" ``` Note that this only declares the inventory, but you still need to make it available by either having it in a git repo or using [file resources](#other-non-inventory-file-resources). Otherwise ansible will fail saying it couldn't find your inventory. ### Other non-inventory file resources It sometimes happens that your Ansible playbook depends on some text file existing at a relative path to the playbook. This can be a configuration file, a template, some other file that you can't inline or otherwise is simpler to keep as a separate file. In this case, Windmill's [plain text file resources](../../../core_concepts/3_resources_and_types/index.mdx#plain-text-file-resources) can be used to create these files at the specified path before running the playbook. The syntax will be the following: ```yaml files: - resource: u/user/fabulous_jinja_template target: ./config_template.j2 ``` In the example above, the resource `u/user/faboulous_jinja_template` is a special plain text file resource. The target `./config_template.j2` is the path relative to the playbook where the file will be created and where the playbook can access it. Now you can write your playbook assuming that this file will exist at the time of execution. #### Variable inside files If you want to achieve a similar effect with a variable or a secret, you can use a similar syntax: ```yaml files: - variable: u/user/my_ssh_key target: ./id_rsa ``` And the content of the variable will be written to the file. This is useful when you want to store the data in a secret for example, like you would do for SSH keys. #### Ansible and SSH To successfully have the playbook SSH, you might need to follow these tips: 1) Write the SSH key into a *secret* variable, and **make sure it has an ending newline**, otherwise you might get an error. ``` -----BEGIN OPENSSH PRIVATE KEY----- MHgCAQEEIQDWlK/Rk2h4WGKCxRs2SwplFVTSyqouwTQKIXrJ/L2clqAKBggqhkjO PQMBB6FEA0IABErMvG2Fa1jjG7DjEQuwRGCEDnVQc1G0ibU/HI1BjkIyf4d+sh 91GhwKDvHGbPaEQFWeTBQ+KbYwjtomLfmZM[...] -----END OPENSSH PRIVATE KEY----- ``` 2) Make a file for the script that will contain this SSH key. Make sure to add the `mode: '0600'` or you might get another error. ```yaml files: - variable: u/user/my_ssh_key target: ./ssh_key mode: '0600' ``` 3) In your inventory file, you'll want to add these : ```ini ... [your_host:vars] ansible_host=your_host ansible_user=john # The SSH user ansible_ssh_private_key_file=ssh_key # The file we declared where the SSH key can be found. ansible_ssh_common_args='-o StrictHostKeyChecking=no' # This skips host key verification, avoiding the error. Alternatively, you can add the host to known_hosts, either as an init script or a task in your playbook ... ``` ### Dependencies Ansible playbooks often depend on Python packages or Ansible Galaxy Collections. In Windmill you can specify these dependencies in the `dependencies` section and Windmill will take care of satisfying them before running the playbook. ```yaml dependencies: galaxy: collections: - name: community.general - name: community.vmware roles: - name: geerlingguy.apache python: - jmespath ``` The syntax is similar to `ansible-builder` and Execution Environments, however all is installed locally using the same technology as for managing [Python dependencies](../../../advanced/15_dependencies_in_python/index.mdx) in Python scripts, meaning no extra container is created. :::info Ansible vs Ansible-core Currently the Windmill image supporting Ansible runs the full `ansible` and not `ansible-core`. You can expect the respective collections to be preinstalled. ::: ### Git repo dependencies Outside of galaxy dependencies, a role or collection can exist on a git repo and be imported as such. The only caveat is that the repo needs to be a valid role or collection at its root. Check the [ansible documentation](https://docs.ansible.com/ansible/latest/collections_guide/collections_installing.html#install-multiple-collections-with-a-requirements-file) for more information. ```yaml collections: - name: git+https://github.com/organization/collection.git type: git version: main ``` To enable more flexibility however, it is possible to declare a git repo to be cloned at a specified location before the playbook is run. You can do this as follows: ```yaml git_repos: - url: git@github.com:some_user/your_git_repo.git target: ./git_repo1 commit: a34ac4fa branch: prod # An https or ssh url can be used: - url: https://github.com/some_user/your_other_git_repo.git target: ./git_repo2 ``` :::info Specifying a commit for your repo If you do not specify the commit to be used, the latest commit hash will be stored in the script lockfile on deployment, and all subsequent executions will use that commit. This is done to ensure reproducibility. If you need to update this, you can simply redeploy the script ::: If you want to clone a private repo, you can add the ssh private key like so: ```yaml git_ssh_identity: - u/user/ssh_id_priv git_repos: - url: git@github.com:some_user/your_private_repo.git target: ./my_roles_and_collections ``` ### Ansible Vault If you have files that are encrypted by ansible vault, you need to pass a password to decrypt them. This can be easily done by storing the password as a Windmill secret, and specifying the path to the secret in the metadata section of your playbook: ```yaml vault_password: u/user/ansible_vault_password ``` If you are using multiple vault password with Vault IDs, the setup is slightly different. You need to define your password files, and also add them as [file resources](#other-non-inventory-file-resources): ```yaml vault_id: - label1@password_filename1 - label2@password_filename2 - label3@password_filename3 files: - variable: u/user/password_for_label1 target: ./password_filename1 - variable: u/user/password_for_label2 target: ./password_filename2 - variable: u/user/password_for_label3 target: ./password_filename3 ``` ### Playbook options Pass `ansible-playbook` flags through the `options` block in the script metadata: ```yaml options: - vvv - forks: 10 - timeout: 30 - flush_cache - force_handlers - limit: webservers:!db1.example.com ``` Supported entries: | Entry | `ansible-playbook` flag | | --- | --- | | `vv` / `vvv` / `vvvv` (1 to 6 `v`s) | `-v` (verbosity) | | `verbosity: vvv` | same as above | | `forks: ` | `--forks` | | `timeout: ` | `--timeout` | | `flush_cache` | `--flush-cache` | | `force_handlers` | `--force-handlers` | | `limit: ` | `--limit` (target a subset of hosts) | The `limit` value may reference a script argument with `{{ argname }}` so a flow can pick the target host at runtime: ```yaml options: - limit: "{{ target_host }}" extra_vars: target_host: type: string ``` ### Delegate the environment setup to a git repo (EE) :::info EE feature Parts of this feature depend on instance-wide blob storage, which is only available in [Enterprise Edition](/pricing). ::: You can choose to set a git repository that contains all your inventories, custom roles, and playbooks as an alternate way to run your ansible script. When declaring this you will get an additional UI that lets you explore the repository, and some helpers to help you define the inventories. You can do this by either declaring this section on the metadata part of the script: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource ``` Or using the utility button that will help you pick a git repo resource to be used. You will need to first create a git_repository resource that points to the repository you're trying to use. You will then see your editor split in two and a Hovering popup indicating the alternate execution mode is detected. The first time you do this the repo viewer will show a button to load the git repository. This will clone and cache the contents of your repository in blob storage, for you to explore the files from within windmill. If you click on the top-right floating pop-up, you will access a screen letting you manage the definition of the git repo, and will contain some utils for ease of use. You can for example use the inventories section to define a subfolder containing your inventories and quickly import the filenames into the script. If you want to set a path to the playbook you want executed, you can do so like so: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource playbook: playbooks/your_playbook.yml ``` If this is undefined, the worker will default to using the second YAML section like normal. #### Inventory from the cloned repo Point ansible at an inventory file (or directory) that lives inside the cloned repository: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource playbook: playbooks/site.yml inventories_location: inventories/dev.ini ``` The path is relative to the repository root. It is added to the `ansible-playbook` invocation as `-i`, alongside any inventories declared in the script metadata. #### Installing requirements.yml from the cloned repo If your repository ships its own `requirements.yml`, set `install_requirements: true` to have Windmill install the listed roles and collections after cloning, before the playbook runs: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource playbook: playbooks/site.yml install_requirements: true ``` The worker looks for `requirements.yml`, `requirements.yaml`, `collections/requirements.yml`, and `roles/requirements.yml` at the root of the cloned repo and runs `ansible-galaxy role install -r` and `ansible-galaxy collection install -r` on every match. If none are found, the step is skipped. This is independent of the `dependencies.galaxy` block in the script metadata: use whichever fits your workflow, or both. #### Using the repo's ansible.cfg Ansible loads a single configuration file, the first one found among `ANSIBLE_CONFIG`, the working directory, `~/.ansible.cfg` and `/etc/ansible/ansible.cfg`, without merging. Windmill runs `ansible-playbook` from the job directory, where it writes its own generated `ansible.cfg`, so a config file checked into the repository is normally ignored. Set `ansible_cfg` to a repo-relative path to make it the effective configuration instead: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource playbook: playbooks/site.yml ansible_cfg: ansible.cfg ``` When set, Windmill points `ANSIBLE_CONFIG` at that file so the repository's settings (roles paths, inventory plugins, callbacks, `host_key_checking`, ssh arguments, etc.) apply as they would outside Windmill. Only the settings that depend on runtime state are layered back on top, through environment variables: - Temp and home directories (`ANSIBLE_HOME`, `ANSIBLE_LOCAL_TEMP`, `ANSIBLE_REMOTE_TEMP`) point to the ephemeral job directory. - Vault settings declared in the script metadata ([`vault_password`, `vault_id`](#ansible-vault)) keep taking precedence over the repo config. - The directories where Windmill installs galaxy roles and collections are prepended to the `roles_path` and `collections_path` declared in your config, so dependencies installed by Windmill (via `dependencies.galaxy` or `install_requirements`) and those shipped in the repo both resolve. The job fails if the file does not exist in the cloned repository. Without `ansible_cfg`, Windmill's generated config takes precedence as before. Like git repo delegation in general, this requires workers running with `DISABLE_NSJAIL=true`. #### Dynamic field values The `playbook`, `commit`, `inventories_location`, and `ansible_cfg` fields accept `{{ argname }}` placeholders that are substituted from the script's arguments before the repo is cloned and the playbook runs. This lets a flow pick the playbook, branch, or inventory at runtime: ```yaml delegate_to_git_repo: resource: u/user/git_repo_resource playbook: "playbooks/{{ playbook_name }}.yml" inventories_location: "inventories/{{ env }}.ini" commit: "{{ git_sha }}" extra_vars: playbook_name: type: string env: type: string git_sha: type: string ``` Only string, number, and boolean argument values can be substituted. The path fields reject absolute paths and `..` segments to prevent escaping the cloned repo. ## Instant preview & testing Look at the UI preview on the right: it was updated to match the input signature. Run a test (`Ctrl` + `Enter`) to verify everything works. Now let's go to the last step: the "Generated UI" settings. ## Generated UI From the Settings menu, the "Generated UI" tab lets you customize the script's arguments. The UI is generated from the Script's main function signature, but you can add additional constraints here. For example, we could use the `Customize property`: add a regex by clicking on `Pattern` to make sure users are providing a name with only alphanumeric characters: `^[A-Za-z0-9]+$`. Let's still allow numbers in case you are some tech billionaire's kid. ## Workflows as code One way to write distributed programs that execute distinct jobs is to use [flows](../../../flows/1_flow_editor.mdx) that chain scripts together. Another approach is to write a program that defines the jobs and their dependencies, and then execute that program directly in your script. This is known as [workflows as code](../../../core_concepts/31_workflows_as_code/index.mdx). ![Flow as code in Python](../../../core_concepts/31_workflows_as_code/wac-editor-1.png "Flow as code in Python") All details at: ## Run! We're done! Now let's look at what users of the script will do. Click on the [Deploy](../../../core_concepts/0_draft_and_deploy/index.mdx) button to load the script. You'll see the user input form we defined earlier. Note that Scripts are [versioned](../../../core_concepts/34_versioning/index.mdx#script-versioning) in Windmill, and each script version is uniquely identified by a hash. Fill in the input field, then hit "Run". You should see a run view, as well as your logs. All script runs are also available in the [Runs](../../../core_concepts/5_monitor_past_and_future_runs/index.mdx) menu on the left. You can also choose to [run the script from the CLI](../../../advanced/3_cli/index.mdx) with the pre-made Command-Line Interface call. ## Caching Every dependency on Python is cached on disk by default. Furthermore if you use the [Distributed cache storage](../../../core_concepts/38_object_storage_in_windmill/index.mdx#instance-object-storage-distributed-cache-for-python-rust-go), it will be available to every other worker, allowing fast startup for every worker. ## What's next? This script is a minimal working example, but there's a few more steps that can be useful in a real-world use case: - Pass [variables and secrets](../../../core_concepts/2_variables_and_secrets/index.mdx) to a script. - Connect to [resources](../../../core_concepts/3_resources_and_types/index.mdx). - [Trigger that script](../../../triggers/index.mdx) in many ways. - Compose scripts in [Flows](../../../flows/1_flow_editor.mdx), [low-code apps](../../../apps/0_app_editor/index.mdx) or [full-code apps](../../../full_code_apps/index.mdx). - You can [share your scripts](../../../misc/1_share_on_hub/index.md) with the community on [Windmill Hub](https://hub.windmill.dev). Once submitted, they will be verified by moderators before becoming available to everyone right within Windmill. Scripts are immutable and there is an hash for each deployment of a given script. Scripts are never overwritten and referring to a script by path is referring to the latest deployed hash at that path. For each script, a UI is autogenerated from the JSON schema inferred from the script signature, and can be customized further as standalone or embedded into rich UIs using the [App builder](../../7_apps_quickstart/index.mdx). In addition to the UI, sync and async [webhooks](../../../core_concepts/4_webhooks/index.mdx) are generated for each deployment. --- ## Bash / PowerShell / Nu quickstart Source: https://www.windmill.dev/docs/getting_started/scripts_quickstart/bash # Bash / PowerShell / Nu quickstart In this quick start guide, we will write our first script in Bash, PowerShell or Nu. This tutorial covers how to create a simple script through Windmill web IDE. See the dedicated page to [develop scripts locally](../../../advanced/4_local_development/index.mdx). Scripts are the basic building blocks in Windmill. They can be [run and scheduled](../../../triggers/index.mdx) as standalone, chained together to create [Flows](../../../flows/1_flow_editor.mdx) or displayed with a personalized User Interface as [Apps](../../7_apps_quickstart/index.mdx). Scripts consist of 2 parts: - [Code](#code). - [Settings](#settings): settings & metadata about the Script such as its path, summary, description, [JSON Schema](../../../core_concepts/13_json_schema_and_parsing/index.mdx) of its inputs (inferred from its signature). When stored in a code repository, those 2 parts are stored separately at `.sh` and `.script.yaml`. Below is a simple example of a script built in Bash/Powershell/Nu with Windmill: ```bash # shellcheck shell=bash # arguments of the form X="$I" are parsed as parameters X of type string url="${1:-default value}" status_code=$(curl -s -o /dev/null -w "%{http_code}" $url) if [[ $status_code == 2* ]] || [[ $status_code == 3* ]]; then echo "The URL is reachable!" else echo "The URL is not reachable." fi ``` ```powershell param($url = "default value") $status_code = (Invoke-WebRequest -Uri $url -Method Get).StatusCode if ($status_code -like "2*" -or $status_code -like "3*") { Write-Host "The URL is reachable!" } else { Write-Host "The URL is not reachable." } ``` ```python def main [ url: string = "default value" ] { try { # Nu will throw an error automatically if request fails http get $url echo "The URL is reachable!" } catch { echo "The URL is not reachable." } } ``` In this quick start guide, we'll create a script that greets the operator running it. From the Home page, click **New** and select **Script**. This will take you to the first step of script creation: [Metadata](../../../script_editor/settings.mdx#metadata). ## Settings ![New script](../../../../static/images/script_languages.png "New script") As part of the [settings](../../../script_editor/settings.mdx) menu, each script has metadata associated with it, enabling it to be defined and configured in depth. - **Summary** (optional) is a short, human-readable summary of the Script. It will be displayed as a title across Windmill. If omitted, the UI will use the `path` by default. - **Path** is the Script's unique identifier that consists of the [script's owner](../../../core_concepts/16_roles_and_permissions/index.mdx), and the script's name. The owner can be either a user, or a group ([folder](../../../core_concepts/8_groups_and_folders/index.mdx#folders)). - **Description** is where you can give instructions through the [auto-generated UI](../../../core_concepts/6_auto_generated_uis/index.mdx) to users on how to run your Script. It supports markdown. - **Language** of the script. - **Script kind**: Action (by default), [Trigger](../../../flows/10_flow_trigger.mdx), [Approval](../../../flows/11_flow_approval.mdx), [Error handler](../../../flows/7_flow_error_handler.md) or [Preprocessor](../../../core_concepts/43_preprocessors/index.mdx). This acts as a tag to filter appropriate scripts from the [flow editor](../../6_flows_quickstart/index.mdx). This menu also has additional settings on [Runtime](../../../script_editor/settings.mdx#runtime), [Generated UI](#generated-ui) and [Triggers](../../../script_editor/settings.mdx#triggers). Now click on the code editor on the left side. ## Code Windmill provides an online editor to work on your Scripts. The left-side is the editor itself. The right-side [previews the UI](../../../core_concepts/6_auto_generated_uis/index.mdx) that Windmill will generate from the Script's signature - this will be visible to the users of the Script. You can preview that UI, provide input values, and [test your script](#instant-preview--testing) there. ![Editor for Bash](./editor_bash.png.webp) ### Bash As we picked `Bash` for this example, Windmill provided some Bash boilerplate. Let's take a look: ```bash # shellcheck shell=bash # arguments of the form X="$I" are parsed as parameters X of type string msg="$1" dflt="${2:-default value}" # the last line of the stdout is the return value echo "Hello $msg" ``` In Bash, the arguments are inferred from the arguments requiring a \$1, \$2, \$3. Default arguments can be specified using the syntax above: `dflt="${2:-default value}"`. The last line of the output, here `echo "Hello $msg"`, is the return value, which might be useful if the script is used in a [flow](../../../flows/1_flow_editor.mdx) or [app](../../../full_code_apps/index.mdx) to pass its result on. ### PowerShell As we picked `PowerShell` for this example, Windmill provided some PowerShell boilerplate. Let's take a look: ```powershell param($Msg, [string[]]$Names, [PSCustomObject]$Obj, $Dflt = "default value", [int]$Nb = 3) # Import-Module MyModule # the last line of the stdout is the return value Write-Output "Hello $Msg" ``` In PowerShell, the arguments are inferred from the param instruction. It has to be first in the script. Arguments can be of type `string`, `int`/`long`/`double`/`decimal`/`single`, `PSCustomObject` (parsed from JSON), `datetime`, `bool` and array of these types. Default arguments can be specified using the following syntax: `$argument_name = "Its default value"`. The last line of the output, here `Write-Output "Hello $Msg"`, is the return value, which might be useful if the script is used in a [flow](../../../flows/1_flow_editor.mdx) or [app](../../../full_code_apps/index.mdx) to pass its result on. ### Nu Unlike `Bash` and `PowerShell`, `Nu` requires main function and all arguments should defined in signature. It supports typed, optional and default arguments. ```python def main [ msg, dflt = "default value", nb: number = 3 ] { echo $"Hello ($msg)" } ``` ```python use std assert # Nushell # A new type of shell def main [ no_default: string, name = "Nicolas Bourbaki", age: int = 42, date_of_birth?: datetime, obj: record = {"records": "included"}, l: list = ["or", "lists!"], tables?: table, enable_kill_mode?: bool = true, ] { # Test # https://www.nushell.sh/book/testing.html assert ($age == 42) print $"Hello World and a warm welcome especially to ($name)" print "and its acolytes.." $age $obj $l print $tables let secret = try { get_variable f/examples/secret } catch { 'No secret yet at f/examples/secret !' }; print $"The variable at \`f/examples/secret\`: ($secret)" # fetch context variables let user = $env.WM_USERNAME # Nu pipelines ls | where size > 1kb | sort-by modified | print "ls:" $in # Nu works with existing data # Nu speaks JSON, YAML, SQLite, Excel, and more out of the box. # It's easy to bring data into a Nu pipeline whether it's in a file, a database, or a web API: let nu_license = http get https://api.github.com/repos/nushell/nushell | get license return { split: ($name | split words), user: $user, nu_license: $nu_license} # Interested in learning more? # https://www.nushell.sh/book/getting_started.html ``` One of the strong sides of `Nu` is that it is cross-platform. If you have linux workers and [windows workers](../../../misc/17_windows_workers/index.mdx) Nushell scripts will be able to run on both! If you are interested in `Nu` you can read their [official documentation](https://www.nushell.sh/book/getting_started.html) ## Instant preview & testing Look at the UI preview on the right: it was updated to match the input signature. Run a test (`Ctrl` + `Enter`) to verify everything works. You can change how the UI behaves by changing the main signature. For example, if you add a default for the `name` argument, the UI won't consider this field as required anymore. ```bash argument_name="${1:-Its default value}" ``` ```bash $argument_name = "Its default value" ``` ```python def main [ argument_name = "Its default value" ] { } ``` Now let's go to the last step: the "Generated UI" settings. ## Generated UI From the Settings menu, the "Generated UI" tab lets you customize the script's arguments. The UI is generated from the Script's main function signature, but you can add additional constraints here. For example, we could use the `Customize property`: add a regex by clicking on `Pattern` to make sure users are providing a name with only alphanumeric characters: `^[A-Za-z0-9]+$`. Let's still allow numbers in case you are some tech billionaire's kid. ![Advanced settings for Bash](./customize_bash.png.webp) ## Run! We're done! Now let's look at what users of the script will do. Click on the [Deploy](../../../core_concepts/0_draft_and_deploy/index.mdx) button to load the script. You'll see the user input form we defined earlier. Note that Scripts are [versioned](../../../core_concepts/34_versioning/index.mdx#script-versioning) in Windmill, and each script version is uniquely identified by a hash. Fill in the input field, then hit "Run". You should see a run view, as well as your logs. All script runs are also available in the [Runs](../../../core_concepts/5_monitor_past_and_future_runs/index.mdx) menu on the left. ![Run Hello in Bash](./run_bash.png.webp) You can also choose to [run the script from the CLI](../../../advanced/3_cli/index.mdx) with the pre-made Command-line interface call. ## JSON result The last line returned by the script will be the string result. To use a json result instead, output your result in `./result.json` and it will be automatically picked-up and considered as the JSON result for Bash and Powershell scripts. For Nu first returned data from main function will be used as a result. ## Run Docker containers In some cases where your task requires a complex set of dependencies or is implemented in a non-supported language, you can still include it as a flow step or individual script. Windmill supports running any docker container through its Bash support with the `# sandbox ` annotation. The image runs inside the job's own nsjail sandbox, daemonless: no Docker socket or Docker-in-Docker sidecar required. ## Run on a remote SSH host A bash script starting with a `#ssh ` directive runs on the remote host described by the referenced `ssh_target` resource instead of the worker, with the same typed arguments, result collection and live logs ([Enterprise Edition](/pricing), off by default). ## What's next? This script is a minimal working example, but there's a few more steps that can be useful in a real-world use case: - Pass [variables and secrets](../../../core_concepts/2_variables_and_secrets/index.mdx) to a script. - Connect to [resources](../../../core_concepts/3_resources_and_types/index.mdx). - [Trigger that script](../../../triggers/index.mdx) in many ways. - Compose scripts in [Flows](../../../flows/1_flow_editor.mdx), [low-code apps](../../../apps/0_app_editor/index.mdx) or [full-code apps](../../../full_code_apps/index.mdx). - You can [share your scripts](../../../misc/1_share_on_hub/index.md) with the community on [Windmill Hub](https://hub.windmill.dev). Once submitted, they will be verified by moderators before becoming available to everyone right within Windmill. Scripts are immutable and there is an hash for each deployment of a given script. Scripts are never overwritten and referring to a script by path is referring to the latest deployed hash at that path. For each script, a UI is autogenerated from the JSON schema inferred from the script signature, and can be customized further as standalone or embedded into rich UIs using the [App builder](../../7_apps_quickstart/index.mdx). In addition to the UI, sync and async [webhooks](../../../core_concepts/4_webhooks/index.mdx) are generated for each deployment. --- ## C# quickstart Source: https://www.windmill.dev/docs/getting_started/scripts_quickstart/csharp # C# quickstart In this quick start guide, we will write our first script in [C#](https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/overview). ![Editor for C#](./editor_csharp.png "Script in C#") This tutorial covers how to create a simple script through Windmill web IDE. See the dedicated page to [develop scripts locally](../../../advanced/4_local_development/index.mdx). Scripts are the basic building blocks in Windmill. They can be [run and scheduled](../../../triggers/index.mdx) as standalone, chained together to create [Flows](../../../flows/1_flow_editor.mdx) or displayed with a personalized User Interface as [Apps](../../7_apps_quickstart/index.mdx). Scripts consist of 2 parts: - [Code](#code): for C# scripts, it must have at least a **public** static Main method inside a class (the name of the class is irrelevant). - [Settings](#settings): settings & metadata about the Script such as its path, summary, description, [JSON Schema](../../../core_concepts/13_json_schema_and_parsing/index.mdx) of its inputs (inferred from its signature). When stored in a code repository, these 2 parts are stored separately at `.cs` and `.script.yaml` Windmill automatically manages [dependencies](../../../advanced/6_imports/index.mdx) for you. When you import libraries in your C# script, Windmill parses these imports upon saving the script and automatically generates a list of dependencies. It then spawns a dependency job to associate these NuGet packages with a lockfile, ensuring that the same version of the script is always executed with the same versions of its dependencies. From the Home page, click **New** and select **Script**. This will take you to the first step of script creation: [Metadata](../../../script_editor/settings.mdx#metadata). ## Settings ![New script](./select_csharp.png "New script") As part of the [settings](../../../script_editor/settings.mdx) menu, each script has metadata associated with it, enabling it to be defined and configured in depth. - **Summary** (optional) is a short, human-readable summary of the Script. It will be displayed as a title across Windmill. If omitted, the UI will use the `path` by default. - **Path** is the Script's unique identifier that consists of the [script's owner](../../../core_concepts/16_roles_and_permissions/index.mdx), and the script's name. The owner can be either a user, or a group ([folder](../../../core_concepts/8_groups_and_folders/index.mdx#folders)). - **Description** is where you can give instructions through the [auto-generated UI](../../../core_concepts/6_auto_generated_uis/index.mdx) to users on how to run your Script. It supports markdown. - **Language** of the script. - **Script kind**: Action (by default), [Trigger](../../../flows/10_flow_trigger.mdx), [Approval](../../../flows/11_flow_approval.mdx), [Error handler](../../../flows/7_flow_error_handler.md) or [Preprocessor](../../../core_concepts/43_preprocessors/index.mdx). This acts as a tag to filter appropriate scripts from the [flow editor](../../6_flows_quickstart/index.mdx). This menu also has additional settings on [Runtime](../../../script_editor/settings.mdx#runtime), [Generated UI](#generated-ui) and [Triggers](../../../script_editor/settings.mdx#triggers). Now click on the code editor on the left side. ## Code Windmill provides an online editor to work on your Scripts. The left-side is the editor itself. The right-side [previews the UI](../../../core_concepts/6_auto_generated_uis/index.mdx) that Windmill will generate from the Script's signature - this will be visible to the users of the Script. You can preview that UI, provide input values, and [test your script](#instant-preview--testing) there. ![Editor for C#](./editor_csharp.png "Editor for C#") As we picked `C#` for this example, Windmill provided some boilerplate. Let's take a look: ```cs #r "nuget: Humanizer, 2.14.1" using System; using System.Linq; using Humanizer; class Script { public static DateTime Main(string[] extraWords, string word = "clue", int highNumberThreshold = 50) { Console.WriteLine("Hello, World!"); Console.WriteLine("Your chosen words are pluralized here:"); string[] newWordArray = extraWords.Concat(new[] { word }).ToArray(); foreach (var s in newWordArray) { Console.WriteLine($" {s.Pluralize()}"); } var random = new Random(); int randomNumber = random.Next(1, 101); Console.WriteLine($"Random number: {randomNumber}"); string greeting = randomNumber > highNumberThreshold ? "High number!" : "Low number!"; greeting += " (according to the threshold parameter)"; Console.WriteLine(greeting); // Humanize a timespan var timespan = TimeSpan.FromMinutes(90); Console.WriteLine($"Timespan: {timespan.Humanize()}"); // Humanize numbers into words int number = 123; Console.WriteLine($"Number: {number.ToWords()}"); // Pluralize words string singular = "apple"; // Humanize date difference var date = DateTime.UtcNow.AddDays(-3); Console.WriteLine($"Date: {date.Humanize()}"); return date; } } ``` In Windmill, scripts need to have a main function that will be the script's entrypoint. There are a few important things to note about the `Main`. - The arguments are used for generating 1. the [input spec](../../../core_concepts/13_json_schema_and_parsing/index.mdx) of the Script 2. the [frontend](../../../core_concepts/6_auto_generated_uis/index.mdx) that you see when running the Script as a standalone app. - Type annotations are used to generate the UI form, and help pre-validate inputs. While not mandatory, they are highly recommended. You can customize the UI in later steps (but not change the input type!). Packages can be installed through NuGet. Just add the dependencies you need at the top of the file, using the following format: ```cs #r "nuget: Humanizer, 2.14.1" #r "nuget: AutoMapper, 6.1.0" ``` :::caution Note that only the lines at the very top will be taken into account. ::: ## Instant preview & testing Look at the UI preview on the right: it was updated to match the input signature. Run a test (`Ctrl` + `Enter`) to verify everything works. ## Generated UI From the Settings menu, the "Generated UI" tab lets you customize the script's arguments. The UI is generated from the Script's main function signature, but you can add additional constraints here. For example, we could use the `Customize property`: add a regex by clicking on `Pattern` to make sure users are providing a name with only alphanumeric characters: `^[A-Za-z0-9]+$`. Let's still allow numbers in case you are some tech billionaire's kid. ![Advanced settings for C#](./customize_csharp.png "Advanced settings for C#") ## Run! We're done! Now let's look at what users of the script will do. Click on the [Deploy](../../../core_concepts/0_draft_and_deploy/index.mdx) button to load the script. You'll see the user input form we defined earlier. Note that Scripts are [versioned](../../../core_concepts/34_versioning/index.mdx#script-versioning) in Windmill, and each script version is uniquely identified by a hash. Fill in the input field, then hit "Run". You should see a run view, as well as your logs. All script runs are also available in the [Runs](../../../core_concepts/5_monitor_past_and_future_runs/index.mdx) menu on the left. ![Run in C#](./run_csharp.png "Run in C#") You can also choose to [run the script from the CLI](../../../advanced/3_cli/index.mdx) with the pre-made Command-line interface call. ## Caching Every binary on C# is cached on disk by default. Furthermore if you use the [Distributed cache storage](../../../core_concepts/38_object_storage_in_windmill/index.mdx#instance-object-storage-distributed-cache-for-python-rust-go), it will be available to every other worker, allowing fast startup for every worker. Instance admins can have the binary built and cached at deployment time rather than on its first run, with [auto-build binaries on deployment](../../../core_concepts/38_object_storage_in_windmill/index.mdx#auto-build-binaries-on-deployment). ## What's next? This script is a minimal working example, but there's a few more steps that can be useful in a real-world use case: - Pass [variables and secrets](../../../core_concepts/2_variables_and_secrets/index.mdx) to a script. - Connect to [resources](../../../core_concepts/3_resources_and_types/index.mdx). - [Trigger that script](../../../triggers/index.mdx) in many ways. - Compose scripts in [Flows](../../../flows/1_flow_editor.mdx), [low-code apps](../../../apps/0_app_editor/index.mdx) or [full-code apps](../../../full_code_apps/index.mdx). - You can [share your scripts](../../../misc/1_share_on_hub/index.md) with the community on [Windmill Hub](https://hub.windmill.dev). Once submitted, they will be verified by moderators before becoming available to everyone right within Windmill. Scripts are immutable and there is an hash for each deployment of a given script. Scripts are never overwritten and referring to a script by path is referring to the latest deployed hash at that path. For each script, a UI is autogenerated from the JSON schema inferred from the script signature, and can be customized further as standalone or embedded into rich UIs using the [App builder](../../7_apps_quickstart/index.mdx). In addition to the UI, sync and async [webhooks](../../../core_concepts/4_webhooks/index.mdx) are generated for each deployment. --- ## dbt quickstart Source: https://www.windmill.dev/docs/getting_started/scripts_quickstart/dbt # dbt quickstart Windmill runs [dbt](https://www.getdbt.com/) projects as a script language of its own. One dbt project is one Windmill script: the project's own files ride with the script as its module bundle, and the worker materializes them into the job directory before invoking dbt. Nothing is cloned at run time, and the project itself is unmodified - the same directory a developer runs `dbt build` against locally. What you get on top of `dbt build` is the rest of Windmill: [scheduling and triggers](../../../triggers/index.mdx), [permissions](../../../core_concepts/16_roles_and_permissions/index.mdx), run history, live per-model progress, and the project's models as first-class [assets](../../../core_concepts/52_assets/index.mdx), so a Python or DuckDB script reading one of its marts appears on the same lineage graph. The runtime, the model graph and the UI are all in the Community Edition. Only the `mssql` and `oracle` adapters require an [Enterprise Edition](/pricing) license, mirroring the boundary the native SQL languages already draw. dbt is in the browser's new-script language picker, and a dbt script opens in [an editor of its own](#the-dbt-editor) rather than the generic one. Projects can equally be imported and edited with the [CLI](../../../advanced/3_cli/index.mdx). dbt is not offered as an inline language, though: a [flow](../../6_flows_quickstart/index.mdx) step and an [app](../../7_apps_quickstart/index.mdx) runnable are a raw body with nowhere to carry a project, so a flow reaches a dbt project the way it reaches any other script - by path, to a deployed one. ## Configure a warehouse A dbt project on Windmill carries no connection of its own. Warehouses are configured once per workspace, under `Workspace settings` -> `dbt`: each entry is a name, a [resource](../../../core_concepts/3_resources_and_types/index.mdx) and an optional dbt target. A project reaches one by name, and takes `main` when it names none. The name is also the warehouse's identity in the asset graph - every model becomes `dbt:////` - so two projects pointing at the same warehouse share their nodes instead of drawing two disconnected islands. ![Workspace settings, dbt tab: a warehouses table with Name, Resource and Target columns - `main` on a Postgres resource with target `prod`, and `lake` on a dbt_profile resource](./warehouse_settings.png 'Warehouses configured under Workspace settings -> dbt') A warehouse points at one of two kinds of resource. **A Windmill connection resource**, when one exists for your warehouse: `postgresql`, `redshift`, `mysql`, `snowflake`, `bigquery` (or `gcp_service_account`) and `databricks`. Windmill translates the fields it carries into the keys dbt reads, so there is nothing dbt-specific to fill in - the resource you already use elsewhere works. The adapter comes from the resource's type. **A `dbt_profile` resource**, for everything else - and for anything the translation above does not cover. It is one entry of a `profiles.yml` `outputs` map, as a resource. Given this file on a developer's machine: ```yaml # ~/.dbt/profiles.yml my_project: target: prod outputs: prod: type: clickhouse host: ch.internal port: 8123 user: analytics password: s3cret schema: marts secure: true ``` the resource *is* the `prod` block - paste it in as it stands, `type` and all: ```json { "type": "clickhouse", "host": "ch.internal", "port": 8123, "user": "analytics", "password": "$var:u/alice/ch_password", "schema": "marts", "secure": true } ``` Nothing is renamed or lifted out, so the block you run locally is the block Windmill runs. The resource type declares no fields, so you get a JSON editor. `$var:` and `$res:` references resolve, including inside nested keys, so credentials stay Windmill secrets. The layers *above* the block are not in the resource, because Windmill already has them: the profile name comes from the project's own `dbt_project.yml`, and which output to use is the warehouse's `Target` (or the descriptor's `profile.target`). A project with a `dev` and a `prod` output becomes two warehouses, one resource each. Every key is handed to dbt unchanged - so **any adapter dbt supports works**, including ones Windmill has never heard of (`trino`, `athena`, `spark`, `fabric`, whatever ships next). Copy the keys from your adapter's own dbt documentation; numbers and booleans keep their type. With the `dbt-core-1x` engine the adapter is installed from PyPI as `dbt-`, and that install is not sandboxed - so it is limited to adapters Windmill ships a list of, plus whatever an instance admin adds to `DBT_EXTRA_ADAPTERS`. `dbt-core-2x` and `fusion` carry their adapters in the binary and install nothing, so they take any `type` at all. Two conveniences on top of a literal target: `root_certificate_pem` is written next to `profiles.yml` and pointed at by `sslrootcert` rather than being sent as a value, and the descriptor's `profile.schema` and `threads` override their keys in the block. The `mssql` and `oracle` adapters need an [enterprise](/pricing) license, whichever kind of resource reaches them - the same boundary the native `ms_sql_server` and `oracledb` script languages draw. Every other adapter is CE. A project that would rather keep its own `profiles.yml` file still can: see [Bring your own profiles.yml](#bring-your-own-profilesyml). Configuring a warehouse is what makes it available: the resource is read on the runner without a per-user permission check, exactly as `s3://` reaches the workspace bucket. Anyone who may run a dbt script may build with the warehouses it names. ## Import a project The project is copied in as-is, into a `