Manage

Role

List, create, update and delete the roles of a workspace from the CLI, and print the permission catalogue a role is built from.

Usage

bash
envless role list [--json] [--workspace <w>]
envless role create <name...> --permissions <a,b,c> [--description <text>] [--workspace <w>]
envless role update <name|id> [new name...] [--description <text>] [--permissions <a,b,c>]
envless role delete <name|id> [<name|id> ...] [--yes] [--workspace <w>]
envless role permissions [--json]

A role is a named set of permissions inside a workspace, and it is what decides who may read variables, invite people, rotate keys or delete a project. envless member hands one to each person with --role, so envless role is the command you run first when the built in roles do not fit. See Concepts for where a workspace sits above products and projects.

The envless command ships in @goenvless/cli. Install it with npm i -g @goenvless/cli@latest.

Every subcommand apart from permissions targets a workspace, and none of them touch variables, so no passphrase and no workspace key are involved.

Running envless role with no subcommand prints the usage block and exits 0. An unknown subcommand prints ✗ Unknown subcommand: <name>, then the same usage block, and exits 2. permissions has no short form, so envless role perms is an unknown subcommand.

Subcommands

SubcommandAlso acceptedTargets
listlsEvery role in the workspace
createadd, newOne new role
updateeditOne role
deleterm, remove, delAs many roles as you pass
permissionsNothing, it prints the catalogue

create and update take a single role, because a role name may contain spaces and every word after the subcommand is read as that name. delete takes a list, and every identifier is resolved before it deletes anything. Repeating an identifier, or passing a name together with the unique ID of the same role, counts once.

Built in roles are read only. update and delete refuse them from the role list they already read, before the API is asked to change anything.

Permissions

bash
envless role permissions
envless role permissions --json

A role is only as useful as the permissions in it, so start here. permissions prints the 44 keys the CLI accepts, grouped by the word before the colon, with no spinner and no prompt box, so it pipes cleanly. The transcript below stops after the fifth group.

bash
$ envless role permissions
workspace
  workspace:update
  workspace:delete

members
  members:read
  members:invite
  members:update
  members:delete

keys
  keys:read
  keys:create
  keys:update
  keys:delete
  keys:rotate

billing
  billing:read
  billing:invoice_read
  billing:manage

roles
  roles:read
  roles:create
  roles:update
  roles:delete

The groups print in catalogue order: workspace, members, keys, billing, roles, products, projects, environments, variables, versions, webhooks. --json prints the flat array of 44 strings and nothing else, which is the list to check a script against. It opens like this:

bash
$ envless role permissions --json
[
  "workspace:update",
  "workspace:delete",
  "members:read"
]

Two keys in the catalogue cannot go into a role you write. workspace:delete and billing:manage are owner only, and the API answers Permission cannot be granted to a custom role: workspace:delete and exits 1. The CLI does not pre-judge that, it only checks the key exists and prints what came back.

List

bash
envless role list

Prints one row per role with the name, whether it is built in, how many permissions it carries and its description. There is no intro block and no spinner, so the output pipes into grep or head cleanly. The header, the name and the system label still carry their colour codes while either stream is a terminal, so set NO_COLOR=1 when a script needs plain text.

bash
$ envless role list
NAME       KIND    PERMS  DESCRIPTION
Admin      system     42  Manages members, keys and roles. Cannot delete the workspace or change billing.
Owner      system     44  Full access to the workspace, including billing and deletion.
Viewer     system     11  Read-only access to the workspace.
QA Reader  custom      2  Read only, no secrets

KIND reads system for the roles a workspace is created with and custom for the ones you add. PERMS is the count, right aligned, and shows - when the API returned no permission array. A role with no description shows - as well. The order is the API's, with the built in roles first and the custom ones after, each group sorted by name. The CLI asks for no other order, so there is no way to sort the table from the command line.

Use --json for scripts. It prints the array as the API returned it, so it carries the fields the table leaves out, including systemKey, isSystem and the timestamps.

bash
$ envless role list --json
[
  {
    "id": 4,
    "uniqueId": "0192c4d1-77aa-7f31-8b02-6d5e91c0a412",
    "workspaceId": "0192a1b2-c3d4-7000-8000-000000000001",
    "name": "QA Reader",
    "description": "Read only, no secrets",
    "systemKey": null,
    "permissions": [
      "projects:read",
      "variables:read"
    ],
    "isSystem": false,
    "deleted": false,
    "deletedAt": null,
    "createdAt": "2026-07-20T09:31:44.000Z",
    "updatedAt": "2026-07-27T18:04:11.000Z"
  }
]

An empty response prints [] under --json, and › No roles in Acme yet. Add one with envless role create. in the table view. Both exit 0. A failed lookup prints ✗ Could not load roles (<error>). and exits 1.

Create

bash
envless role create QA Reader --permissions projects:read,variables:read
envless role create --name 'QA Reader' --permissions projects:read --description 'Read only, no secrets'

The name can be bare words after create, joined with single spaces, or --name. Bare words win when you pass both. create never prompts, so a run with no name at all stops with ✗ Missing a name. Run envless role create <name> --permissions <a,b,c>. and exits 2.

--permissions is required, because a role with nothing in it cannot do anything. Leaving it out reports ✗ Missing --permissions. A role with no permissions cannot do anything. Run envless role permissions to see all 44. and exits 2.

bash
$ envless role create QA Reader --permissions PROJECTS:READ,variables:read,projects:read --description 'Read only, no secrets'
┌  envless role create
✔ Added to Acme
│
└  ✓ QA Reader created with 2 permissions.

The list is split on commas, each entry is trimmed, empty entries are dropped, repeats are collapsed and matching is case insensitive, so PROJECTS:READ and projects:read are the same key and are sent once, in lower case. All of that happens before the first network call, and so does the name check.

Names are 3 to 50 characters. Under that fails with A role name needs at least 3 characters. and over it with A role name can be 50 characters at most., both exiting 2. An unknown entry names every one it did not recognise, as you typed it, and exits 2. A list that comes out empty, such as --permissions ' , ', is refused the same way.

bash
$ envless role create Auditor --permissions variables:read,nope:read
✗ Not a permission: nope:read. Run envless role permissions to see all 44.

$ envless role create Auditor --permissions ' , '
✗ --permissions was empty. Run envless role permissions to see all 44.

Without --description the role is created with no description at all. Quote a description with spaces, since the flag reads one token.

The CLI's 3 to 50 character rule is not the API's. The API accepts 2 to 40, so a 45 character name passes the local check and comes back as Role name should be less than 40 characters long. with exit 1. A description over 200 characters comes back as Role description should be less than 200 characters long.

Server-side refusals are printed as they came back and exit 1. A name clash reads A role with this name already exists in the workspace. and is matched without case. A workspace already holding 25 custom roles reads You have reached the maximum of 25 custom roles for this workspace. Asking for a permission you do not hold yourself reads You cannot grant permissions you do not hold yourself: keys:rotate. Missing permission reads The user is not authorized to create workspace roles.

Update

bash
envless role update 'QA Reader' QA Read Only
envless role update 'QA Reader' --description 'Read only, no secrets'
envless role update 'QA Reader' --permissions projects:read
envless role update 'QA Reader' --description ''

The first word after update is the role, given as its exact name or unique ID, matched without case. Roles have no slug and there is no partial matching. Everything after the identifier is the new name, joined with single spaces, and --name is read only when no words follow the identifier. Pass a new name, --description, --permissions, or several in the same run.

bash
$ envless role update 'QA Reader' QA Read Only
┌  envless role update
✔ QA Reader (0192c4d1-77aa-7f31-8b02-6d5e91c0a412)
✔ QA Read Only (0192c4d1-77aa-7f31-8b02-6d5e91c0a412)
│
└  ✓ Renamed to QA Read Only.

Missing the role reports ✗ Missing the role to update. Run envless role update <name|id> <new name>. and exits 2. Passing nothing to change reports ✗ Nothing to update. Pass a new name, --description <text> or --permissions <a,b,c>. and also exits 2. The 3 to 50 character rule applies to the new name and is checked before any request goes out.

Only the fields that differ are sent. A new name identical to the current one drops out of the patch, and when it was the only thing you passed the command prints Already set that way, nothing changed., writes nothing, and exits 0. --description and --permissions are always sent when passed, since the CLI has nothing to compare a permission set against without asking for it.

--permissions replaces the whole set rather than adding to it, and the outro says so, so there is no way to append one key without listing the ones you keep.

bash
$ envless role update 'QA Reader' --permissions projects:read
┌  envless role update
✔ QA Reader (0192c4d1-77aa-7f31-8b02-6d5e91c0a412)
✔ QA Reader (0192c4d1-77aa-7f31-8b02-6d5e91c0a412)
│
└  ✓ Updated QA Reader. It now has exactly 1 permission, the old set was replaced.

An empty --permissions is refused instead of clearing the set, with ✗ --permissions was empty. A role with no permissions cannot do anything, so leave the flag out to keep the current set. and exit 2. To clear the description, pass --description '', and the outro adds The description is gone.

Use --description '' with a space before the empty quotes. The --description= form with nothing after the equals sign is read as a switch rather than a value, so it counts as not passed and leaves the description alone.

A built in role is refused before the API is asked to change anything.

bash
$ envless role update Owner --description 'The boss'
┌  envless role update
✖ Owner is a built in role and cannot be changed. Create your own with envless role create.
│
■  Aborted.

A role the workspace does not hold fails the same way, with Role QA Reader not found in Acme. then Aborted., and exits 1. Server-side refusals exit 1 and print the API message, which for the built in roles the CLI already caught reads System roles cannot be updated., and for a clash reads A role with this name already exists in the workspace.

Delete

bash
envless role delete 'QA Reader'
envless role delete 'QA Reader' 'Legacy Reader' --yes

Roles are matched by exact name or unique ID, without case. The table envless role list prints carries the name, and envless role list --json is where the unique ID is. Every identifier is resolved from one role list before the confirmation, so a miss stops the run before anything is deleted, with Role Legacy Reader not found in Acme. for one identifier and Roles not found in Acme: Legacy Reader, Old QA for several. Both exit 1.

bash
$ envless role delete 'QA Reader'
┌  envless role delete
✔ QA Reader
│
◇  Delete QA Reader? Anyone holding it needs a new role first.
│  Yes
│
✔ Removed from Acme
│
└  ✓ QA Reader deleted.

There is one confirmation for the whole set and it names every role in it. The default answer is No. Declining it, or interrupting it, prints Aborted. and exits 1, so a refusal never reads as success. Pass --yes or -y to skip the prompt.

A built in role anywhere in the set stops the whole run before the confirmation, so nothing is deleted, not even the custom roles you passed alongside it.

bash
$ envless role delete Admin 'QA Reader' --yes
┌  envless role delete
✖ Admin is a built in role and cannot be deleted.
│
■  Aborted.

Several built in roles read Built in roles cannot be deleted: Admin, Viewer instead. Both exit 1.

Once the set has resolved and the confirmation is answered, deletes run one at a time and a refusal on one role never stops the rest. The outro counts what happened and the exit code is 1 when anything was kept.

bash
$ envless role delete 'QA Reader' 'Legacy Reader'
┌  envless role delete
✔ QA Reader, Legacy Reader
│
◇  Delete QA Reader, Legacy Reader? Anyone holding them needs a new role first.
│  Yes
│
✔ Removed QA Reader
✗ Legacy Reader was kept (This role is assigned to 2 member(s). Reassign them before deleting.)
│
└  ! 1 deleted, 1 kept.

When every role was kept, the run ends on Aborted. instead of a count, and also exits 1.

A role in use cannot be deleted, and the CLI prints the API's own reason rather than checking first. This role is assigned to 2 member(s). Reassign them before deleting. means you need envless member update <email> --role <name> for each of them. This role is attached to 1 pending invite(s). Revoke them before deleting. means the invite has to go first.

Without --yes on a non-TTY the command stops before deleting anything with ✗ Refusing to run unattended. Pass --yes to confirm. and exits 1. That is the flag to add in CI.

Creating, updating or deleting a role is also a step-up guarded action. Unless you verified a code for this workspace in the last hour, the CLI prompts for one after the confirmation, verifies it, and reruns the write once. --yes does not skip the code. Without a TTY the write fails with This action needs a verification code. Run it from an interactive terminal, or do it from the dashboard., so a job that has to run it unattended needs the workspace's Verification Codes setting turned off, see Workspace.

Flags

FlagDescription
--permissions <a,b,c>Comma separated permission keys. Required on create. On update it replaces the whole set. Matched without case against the 44 keys envless role permissions prints, and validated before any request.
--name <name>Name for a new role on create, or the new name on update. Bare words after the subcommand take precedence over it. 3 to 50 characters.
--description <text>One line on what the role is for, on create and update. Quote it when it has spaces. --description '' clears it on update, and leaving it out on update keeps what is there.
--jsonlist and permissions only. Prints JSON and nothing else, with no spinner and no colour.
--yes, -ydelete only. Skips the confirmation prompt. Required when output is not a TTY.
--workspace <w>Workspace slug or unique ID. Falls back to the workspace field of .envless, then to the default set by envless workspace use. Not read by permissions.

Requirements

list, create, update and delete need you signed in with envless login. No passphrase, ENVLESS_KEY, --project or --env is needed, because these commands read and write role records rather than variables.

The workspace comes from --workspace first, the .envless file second, and the default set by envless workspace use third. With none of them, the command stops on ✗ Missing --workspace. Pass it inline, run envless link, or pick a default with envless workspace use <slug|id>. and exits 1. permissions is the exception. It prints a catalogue built into the binary and resolves nothing at all, so it runs signed out, with no .envless file, no default workspace and no --workspace.

list, create, update and delete run through the write context, so an ev_sk_ machine key is rejected with ✗ Machine keys are read only. Run envless login to write from this machine. even for envless role list. envless role permissions never looks at a token, so it prints the catalogue and exits 0 whatever key is in the environment. Machine keys are for reading variables, as in envless sync. See Keys.

The write context also resolves a product when one is named, and these commands never use it. So a stale product entry in .envless, a stale ENVLESS_PRODUCT, or a --product flag pointing at something deleted makes list, create, update and delete fail on that lookup with ✗ Product <name> not found in Acme. permissions never reaches it. Clear the value or re-run envless link. See envless product for the products themselves.

Exit codes

CodeWhen
0Success, an empty role list, an update that changed nothing, and bare envless role printing its usage
2Unknown subcommand, create with no name, a name outside 3 to 50 characters, create without --permissions, an unknown or empty permission list, update with no role or nothing to change, and delete with no role
1Not signed in, machine key rejected, unresolved workspace or product, a role that does not resolve, a built in role passed to update or delete, a cancelled prompt, a declined or unattended confirmation, any API failure, and a delete run where at least one role was kept

Errors

SituationBehavior
Not signed in✗ Not signed in. Run envless login first. and exit 1
ev_sk_ machine key in use✗ Machine keys are read only. Run envless login to write from this machine. and exit 1
No workspace from flag or file✗ Missing --workspace. Pass it inline, run envless link, or pick a default with envless workspace use <slug|id>. and exit 1
Workspace does not resolve✗ Workspace acme not found. or ✗ Could not resolve workspace (<error>). and exit 1
Role list could not be read✗ Could not load roles (<error>). and exit 1
create with no name✗ Missing a name. Run envless role create <name> --permissions <a,b,c>. and exit 2
create without --permissions✗ Missing --permissions. A role with no permissions cannot do anything. Run envless role permissions to see all 44. and exit 2
An entry is not a permission✗ Not a permission: nope:read. Run envless role permissions to see all 44. and exit 2
--permissions resolves to nothing✗ --permissions was empty. Run envless role permissions to see all 44. on create, and ✗ --permissions was empty. A role with no permissions cannot do anything, so leave the flag out to keep the current set. on update. Both exit 2
Name outside 3 to 50 characters✗ A role name needs at least 3 characters. or ✗ A role name can be 50 characters at most. and exit 2
update with no role✗ Missing the role to update. Run envless role update <name|id> <new name>. and exit 2
update with nothing to change✗ Nothing to update. Pass a new name, --description <text> or --permissions <a,b,c>. and exit 2
update on a built in roleFails the spinner with Owner is a built in role and cannot be changed. Create your own with envless role create., prints Aborted., exits 1
update target not foundFails the spinner with Role <name> not found in Acme., prints Aborted., exits 1
delete with no name or ID✗ Missing a role. Run envless role delete <name|id>, or envless role list to see them. and exit 2
delete target not foundFails the spinner with Role <name> not found in Acme., or Roles not found in Acme: <names> for several, prints Aborted., exits 1
delete on a built in roleFails the spinner with <name> is a built in role and cannot be deleted., or Built in roles cannot be deleted: <names> for several, prints Aborted., exits 1. Nothing in the set is deleted
delete declinedPrints Aborted. and exits 1, nothing is deleted
delete refused on one role<name> was kept (<error>), the rest still run, the outro reads ! 1 deleted, 1 kept., exit 1
delete refused on every rolePrints Aborted. and exits 1
delete unattended without --yes✗ Refusing to run unattended. Pass --yes to confirm. and exit 1
API rejects the writePrints the API message as it came back and exits 1

Going deeper

The permission model behind these commands, including where the boundary is authorization rather than cryptography.