Roles and permissions
Windmill provides a roles and permissions system that allows you to control access and manage permissions within your instance and workspaces. Different roles have different levels of access to scripts, flows, apps, and other entities in Windmill.
Everybody having access to a Windmill instance is considered a User.
Recap of how roles within an instance and a workspace are structured:

Recap of how are permissioned items (scripts, flows, apps, resources, variables, schedules, jobs) within a workspace:

Users
Users are uniquely identified globally by their email. They also have a unique username with respect to each workspace they are members of.
In terms of billing for Windmill Enterprise Edition, we only count active users, i.e. users who have at least logged in to the platform in the last 30 days according to the audit logs. Logging in once in 30 days is enough to be considered 'Active'. A user counts as a full seat when, in at least one workspace they are a member of, they are an admin or their workspace role can write code or build flows or apps. Every other user, whose roles only let them run, view and manage what others built, counts as half a seat. Service accounts are the exception: they always count as half a seat. For external JWT authentication, a unique JWT user is counted per (username/email, scope, instance) tuple used in the last 30 days - the same email with two different scopes, or on two different instances, counts as two JWT users. Each unique JWT user is billed as half a seat.
Guests have no account at all and are not counted as users. They count against a separate guest allowance instead.
Workspace
Every nameable or pathable entity in Windmill has a workspace attached to it. This includes:
- users
- groups
- scripts
- resources
- variables
- schedules
- jobs
Windmill's entire database is partitioned by workspaces such that users, teams and orgs can safely co-locate without risk of leakage.
Any user can create their own workspace. When a user creates a workspace, the user is an admin of such workspace and he can invite others to join his workspace.
Roles in Windmill
Users can be given different levels of permissions:
Superadmin
The superadmin role has the highest level of access and can manage the entire Windmill instance. Only superadmins can access the dedicated 'admins' workspace. Also by default, superadmins have access to all workspaces as admins of those workspaces. Windmill can be configured to remove the ability for non-superadmins to create workspace using the: CREATE_WORKSPACE_REQUIRE_SUPERADMIN env variable.
When a superadmin does an action in a workspace they are not a member of, they have no username in that workspace. Their instance-wide derived username (e.g. admin for [email protected], assigned through automatic username creation) is used to identify them in job metadata (created_by, permissioned_as), audit logs, draft authorship and the UI, and their email is still preserved internally (e.g. in permissioned_as_email on jobs). The UI shows 'Superadmin, not a member of this workspace' along with the acting username. Only on very old legacy instances where automatic username creation is disabled is their email used in lieu of the username.
Devops
The devops role is also set at the instance level. It is meant for the people who operate a Windmill instance: they see how the instance itself is running, but not what runs on it. A devops user can:
- See the workers, queue metrics and heaviest scripts, edit and delete worker group configs (with object storage credentials masked) and restart worker groups.
- Read the Service Logs.
- See and acknowledge instance-wide Critical Alerts (Enterprise Edition).
The role grants no access to workspaces and their content (scripts, flows, apps, resources, variables, runs) and no access to the instance settings or the instance users list, which remain superadmin-only. Contrary to the superadmin, it does not supersede workspace roles: to work inside a workspace, a devops user must be added to it, and gets the role given there.
A devops user who is not a member of any workspace reaches the Workers and Service Logs pages from the "Instance" section of the workspace selection page. Members of a workspace also find them in the workspace's sidebar.
Instance-level roles via instance groups
Superadmin and devops roles can also be assigned to instance groups. When an instance group has an instance role, all members of that group automatically receive that role. This is particularly useful with SCIM — for example, an "infrastructure" group in your identity provider can automatically grant devops access to all its members.
Role precedence rules:
- If a user belongs to multiple instance groups with different roles, the highest role wins (superadmin > devops > user).
- Manually-set elevated roles (superadmin or devops) always take precedence over group-assigned roles. A superadmin manually promoted by another superadmin will not be demoted if their instance group only grants devops.
- To demote a user whose role is set by an instance group, the user must be removed from the group (or the group's instance role must be changed).
With login-time group sync enabled (Enterprise Edition), membership in SCIM-provisioned groups is also re-evaluated from the identity provider's groups claim at each SSO login, so a role granted or revoked through a group applies at the user's next login instead of waiting for the next SCIM push.
In the instance settings users list, roles set by instance groups are indicated with a "Set by instance group" label.
Admin
At the workspace level, admins have the ability to manage a specific Windmill workspace and users within that workspace. They can see and edit all scripts, flows, apps, and other entities within the workspace. Admins have the power to create, modify, and delete entities and can manage permissions within the workspace.
At the folder level, an admin of a folder has read, write and archive access to all the elements inside the folders (scripts, flows, apps, resources, schedules) and can manage the permissions as well as add new admins.
Workspace roles
Every member of a workspace holds a workspace role, which decides what they can author, what they can manage and which pages they see. You pick it when adding or inviting a member, for auto-added users and for instance groups mapped to the workspace. When several sources name a role for the same user, the highest-ranked one wins: admin first, then the order of the roles in the instance settings.
Windmill ships with two default roles:
- Developer: every capability below. New members get it by default.
- Operator: runs and views what others built, without authoring scripts, flows or apps. It can still manage schedules and triggers and sees every page.
Admin is not a workspace role: it is a flag on top of the member's role, and an admin holds every capability whatever role they have underneath.
Capabilities
A role is a set of capabilities, edited from the role settings:
| Capability | Values | What it allows |
|---|---|---|
| Write code | On / Off | Author scripts and run code of one's own on a worker: script previews, dependency jobs, dynamic selects, resolving a job's code, data table migrations and AI agent eval datasets. Required for full flow or app authoring. |
| Flows | None / Compose / Full | Compose assembles already deployed scripts and flows into flows, without code of its own: the flow editor only offers workspace runnables as steps, and the server refuses inline scripts and Hub runnables. Full is the complete flow editor. |
| Apps | None / Compose / Full | Compose builds full-code apps, frontend code included, whose backend runnables are existing workspace scripts or flows the member can read. Such an app is always deployed with sandbox isolation. Low-code apps need Full. |
| Manage schedules | On / Off | Create, edit and delete schedules where folder permissions allow. Without it, members still see schedules but the server refuses any change, including through the API and the CLI. |
| Manage triggers | On / Off | Create, edit and delete triggers where folder permissions allow. Without it, members still see triggers but the server refuses any change, including through the API and the CLI. |
| Page visibility | On / Off per page | Show Runs, Schedules, Resources, Variables, Assets, Triggers, Audit logs, Groups, Folders and Workers in the UI. |
Full flows or apps require Write code, since both can carry inline code. Page visibility only hides pages: it does not stop an API call, and the enforced rights are the other capabilities. A role that cannot write code or build flows and apps cannot deploy those items to another workspace either, and the integration pickers of native triggers need both Write code and Manage triggers.
A role restricts authoring, not access to data. Everything else follows the regular permission model: within the paths and folders they have write access to, members of any role can create and edit variables, resources, folders and groups, exactly like any other member. If a member needs to be strictly read-only, grant them read-only access to the folders and paths they need, and do not rely on the role for it.
The recommended way to share scripts and flows with members who do not author is through auto-generated apps. The second option is to share the script and the variables it depends on. A member with read access to a variable can read its value, secrets included, through the API. The UI hides the "Load secret value" button for roles that cannot write code, but that is a UI convenience, not a permission boundary. Grant folder access accordingly.
Define the instance roles
From instance settings > Workspace roles, a superadmin can replace the two default roles with any list of roles, as long as one remains. Each role has a name, a label shown in the role pickers and its capabilities. The order of the list ranks the roles, the first one highest. A role still held by a member, an invite, the auto-add setting or an instance group mapping cannot be removed, and neither can a role that a workspace still overrides.
Custom instance roles require an Enterprise Edition license. If the license lapses, the stored roles keep applying unchanged but can no longer be edited, apart from resetting the list to the default roles.
Override roles in a workspace
From workspace settings > Users > Workspace roles, a workspace admin can override what each role allows in that workspace. A capability that is not overridden keeps following the instance definition, and each one can be reset to it on its own. This is available on every edition, for the default roles as well.
On Enterprise Edition, a workspace can also add roles of its own with "Add workspace role". Capabilities left unset on such a role default to those of the Operator role.
Role overrides are not yet carried by Git sync or workspace export. A role change can take up to two minutes to reach every server.
Roles and seats
A role that can write code or build flows or apps, at the Compose or Full level, consumes a full seat. Any other role consumes half a seat, unless the member is an admin, who always consumes a full seat. When a change to a role makes it author something, saving it asks you to confirm that the members holding it now consume a full seat.
Service accounts
Service accounts are workspace-scoped automation identities that cannot log in directly (no password, no SSO). They act through tokens and are intended for CI/CD pipelines, scheduled jobs, or API integrations that need workspace-scoped permissions without a real user login.
Service accounts are an Enterprise feature. Create them from workspace settings under user management: in "Add new user", turn on the "Service account" toggle. Only a username is required - the email is derived automatically, so there is nothing to invite or verify.
You pick its role at creation time:
- Operator (default): the default role that runs but does not author. Cannot be used for CLI sync or to author scripts, flows and apps.
- Developer: the default role that can author and edit scripts, flows and apps within its path. This is the role to use for CLI sync tokens.
- Admin: full workspace admin. Grant only when the service account needs to manage workspace settings.
When you pick Developer, you can also add the service account to the wm_deployers group. This is recommended when the account is used as a wmill sync push / CI deploy identity, since members of wm_deployers can deploy on behalf of other users in the target workspace.
Admins can impersonate a service account to act as that identity and verify its permissions.
Unlike regular members, a service account counts as half a seat whatever its role, Developer and Admin included.
Anonymous app viewers
Anonymous app viewers are individuals who can access and view Windmill apps without being a user but by knowing the secret URL that allows app viewing. They do not have any editing or privileges to execute scripts and flows. The apps that they see execute the scripts and flows that are part of the app but only on behalf of the app author. The anonymous user cannot execute any of the scripts and flows part of the apps outside of the inputs authorized by the app through a policy that is automatically generated by the app.
Guests
Guests are individuals with no Windmill account, members of no workspace, whom your identity provider authenticates or an embedding backend names in a signed JWT. A guest can open an app deployed in guest execution mode and nothing else: no listing of jobs, scripts, flows, apps or variables, no other app, no other workspace. Like for an anonymous viewer, the app's runnables execute on behalf of the app publisher, within the app's automatically generated policy.
Entering as a guest creates no user and no workspace membership, so guests do not take a seat. They count instead against an instance-wide allowance: the first 100 distinct guest emails over a trailing 30 days are free everywhere, and past that they are either metered or refused depending on the instance's license. See seats and the guest allowance.
Someone who already has an account anywhere on the instance is never admitted as a guest, and a guest who later signs in from the ordinary login page becomes a regular user, counted like any other.
Permissions and access control
Windmill implements a fine-grained Access Control List (ACL) by default, allowing you to define permissions for various entities within the system, including:
There are 3 possible roles for each item:
- Admin (Owner): has read, write and archive access. Admins of an item can't delete it unless they are admins of the workspace.
- Writer: has read, write and archive access. They can't archive nor change the path of the item.
- Viewer: has read-only access to the item.
Admins of a workspace have elevated privileges and can read and write over everything within the workspace, disregarding ACLs.
The ACLs are defined by the path of the item.
Path
Windmill uniquely identifies scripts, variables, resources, schedules - and in general almost everything - using their path. The paths are globally unique within the category of entities they represent. In short, a Resource and a Schedule for example can have the same path, without conflict.
Each entity's ACL defines its owner and specifies the read and write permissions. By default, only the owner of an entity has read and write access to it. Entities can be explicitly shared with other groups and users in either read-only or read and write mode through folders. Write mode implicitly includes read permission.
A path is either inside a user space u/<user>/<path> or inside a folder f/<folder>/<path>. The path defines permissions with:
- a User: that user will be by default given read, write and archive access on the item (
u/henri/amazed_postgresql). - a Folder: the item will be available to the users having access to the folder, with its own levels of permissions (
f/data_team/amazed_postgresql).
You can modify paths, but this may break other dependent entities. For scripts, flows, and apps, you will see a warning with a list of affected entities:

Extra permissions
For each item (script, flow, app, resource, variable, schedule), you can define extra permissions clicking on the ⋮ icon, then Share.
Extra permissions can be given to a user or a group, with the possibility to give them the role of Viewer, or Writer.
For scripts, flows, apps and variables, those extra permissions are also tracked in source as an extra_perms map, so they can be reviewed and deployed like the rest of the workspace through Git sync or the CLI.


Example of permissions
For example here a new SQL Resource is being given access to a user username with view, write and archive access:

Here the new SQL Resource is being given access to a folder data_team...

... folder that can give Admin, Writer or Viewer accesses to users or groups.

Also, extra permission was given to Ruben as viewer.

Run on behalf of
Scripts and flows can be configured to run on behalf of a specific user. When set, that user's permissions are used during execution — the runnable can only access the resources, variables, and folders that user has rights to — and all runs are attributed to that user in the audit logs.
The setting itself lives in the script or flow's advanced settings ("Run on behalf of"), but you don't need to open the editor to see it: the detail page header of a runnable that has it set shows an On behalf of u/<user> badge naming the identity every run is permissioned as, whoever starts it. Members whose role cannot open the editor see the badge too.
For apps, run on behalf of is always enabled: app executions always use the permissions of the set user (usually the app's last editor).
Schedules and triggers work in a similar manner: they use the edited_by user as the owner of the executions they create.
Permission compartmentalization with virtual users
By combining the run on behalf of mechanism with dedicated users, you can implement fine-grained permission compartmentalization. Instead of running everything as a single admin or shared account, create users scoped to specific logical modules. For example:
- A
billing-botuser with access only to billing-related resources and folders - A
data-pipelineuser scoped to data processing folders and their associated variables
Then set scripts, flows, schedules, and triggers to run on behalf of these virtual users. A script running on behalf of billing-bot cannot accidentally read or modify resources outside its scope, even if the script code itself contains a bug or misconfiguration. The audit logs will also clearly show which virtual user initiated each execution.
Combined with the "run on behalf of" selector during deployments, this pattern extends across workspaces: when deploying to a production workspace, you can assign each item to the appropriate virtual user in the target environment.
User offboarding
When removing a user, you can reassign all their items to another user or folder so that schedules, triggers, and everything else keeps working.
From Workspace settings > Users, click Remove on a user. The modal shows what they own, what runs on their behalf, and what references their paths. Pick a target user or folder, confirm, and everything is moved in one step.

You can also use Reassign to transfer ownership without removing the user — useful when moving personal scripts into shared folders.

If the target already has items at the same paths, the operation is blocked until you resolve the conflicts.
Instance-level offboarding
Superadmins can offboard a user across all workspaces at once from Instance settings > Users, with a different reassignment target per workspace.
