Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions guides/developer/dashboards-as-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,79 @@ And the matching tile entry on a dashboard:
tileSlug: orders-by-status
```

## Custom roles as code

[Custom roles](/references/workspace/custom-roles) are also managed as code. Unlike charts and dashboards, custom roles are **organization-scoped** rather than project-scoped, so downloading and uploading them uses the `--organization` mode of `lightdash download` and `lightdash upload` and requires an organization admin.

### When to use it

- Version-control your organization's custom roles alongside charts and dashboards.
- Copy a role from one Lightdash instance (for example, staging) to another (production) without recreating it by hand in the UI.
- Review role changes in a pull request before they hit the org.

### YAML contract

Each custom role serializes to a single file under `lightdash/custom-roles/<role-slug>.yml`:

```yaml
version: 1
name: analytics-viewer
description: Read-only access to analytics content
level: organization
scopes:
- view:Dashboard
- view:SavedChart
- view:Space
```

| Field | Description |
| --- | --- |
| `version` | Format version. Currently always `1`. |
| `name` | The role's display name. This is the portable identity — a role is matched to an existing role by exact name within the organization. |
| `description` | Optional human-readable description. Set to `null` to clear an existing description. |
| `level` | Either `organization` or `project`. Determines which scopes are allowed. Immutable — once a role is created, its level cannot be changed via upload. |
| `scopes` | The list of scope grants for the role. See the [scope reference](/references/workspace/custom-roles#scope-reference) for available scopes. |

The filename is derived from the role name; the CLI creates safe filenames and disambiguates collisions automatically.

### Download

Run the download in `--organization` mode from your working directory:

```bash
lightdash download --organization
```

This writes every user-defined custom role in your organization to `lightdash/custom-roles/*.yml`. System roles are not included. The `--organization` flag switches the command into organization-content mode and cannot be combined with project-content flags such as `--charts`, `--dashboards`, or `--project`.

### Upload

Run the upload in `--organization` mode from the same working directory:

```bash
lightdash upload --organization
```

For each file in `lightdash/custom-roles/`, the backend resolves the role by `name` and returns one of three outcomes:

- **Created** — no role with that name exists yet, so a new one is created.
- **Updated** — the role exists and either the description or the set of scopes has changed.
- **Unchanged** — the role exists and already matches the file. No write is performed.

Uploads are per-file. A malformed or invalid file is reported with its file path and the error message from the backend, and the remaining files continue to process. If any file fails validation, the CLI exits with a non-zero status.

<Note>
Uploads never delete roles. Removing a `.yml` file from `lightdash/custom-roles/` and re-uploading does not delete the role in Lightdash — delete unwanted roles in the UI.
</Note>

### Permissions and validation

- The user running `lightdash download --organization` and `lightdash upload --organization` must be an **organization admin**.
- The backend validates the role's version, name, description, level, and scopes on every upload. It rejects unknown fields, unsupported versions, unknown scopes, and scopes that aren't allowed at the role's level.
- The role level is immutable — you cannot change an existing organization role into a project role (or vice versa) by editing the YAML and uploading. Create a new role instead.

For backwards compatibility, an existing role may retain a legacy scope that is no longer assignable at its level; downloading and re-uploading such a role is a no-op. Scope-level validation is applied only to newly added scopes.

## Editing with AI agents

You can use AI coding assistants like Cursor, Claude Code, and Codex to create and edit charts using natural language instead of clicking through the UI.
Expand Down
18 changes: 18 additions & 0 deletions references/lightdash-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,9 @@ You can make changes to the code and upload these changes back to your Lightdash
- `--skip-dashboards`
- (default: false)
- skip downloading dashboards
- `--organization`
- (default: false)
- switch to organization mode and download organization-scoped content (currently [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code)) instead of project content. Files are written to `lightdash/custom-roles/<role-slug>.yml`. Requires organization admin permissions. Cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, or `--nested`.

**Examples:**

Expand Down Expand Up @@ -769,6 +772,12 @@ Download just one data app and nothing else.
lightdash download --apps-only --apps 8f2b1c4d-1111-2222-3333-444455556666
```

Download every organization-scoped custom role as code (requires org admin).

```bash
lightdash download --organization
```


### `lightdash upload`

Expand Down Expand Up @@ -812,6 +821,9 @@ If there have been changes made to a chart or dashboard in the application that
- `--create-new`
- (default: false)
- always create a new data app from the uploaded source instead of appending a new version to the app referenced by `lightdash-app.yml`. Combine with `--apps` and `--project` when copying an app to a different project or instance in a non-interactive shell.
- `--organization`
- (default: false)
- switch to organization mode and upload organization-scoped content (currently [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code)) instead of project content. Reads YAML files from `lightdash/custom-roles/` and upserts each role by name — creating, updating, or reporting no change per file. Requires organization admin permissions. Cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, or `--force`. Uploads never delete roles.

**Examples:**

Expand Down Expand Up @@ -877,6 +889,12 @@ Copy a data app to a different project as a brand-new app (non-interactive).
lightdash upload --apps --project 21eef0b9-5bae-40f3-851e-9554588e71a6 --create-new
```

Upload every custom role in `lightdash/custom-roles/` back to the organization (requires org admin).

```bash
lightdash upload --organization
```


### `lightdash rename`

Expand Down
51 changes: 51 additions & 0 deletions references/workspace/custom-roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,57 @@ Three scopes control different SQL-authoring features. They are independent —
If you want a user to query tables that include custom SQL dimensions created by others, they don't need any of these scopes. Any user with access to the Explore view can **use** existing custom SQL dimensions in their queries — these scopes only control who can **author** them.
</Tip>

## Managing custom roles as code

Custom roles can be downloaded from and uploaded to a Lightdash instance as YAML files via the CLI, alongside charts, dashboards, and other content as code. This is useful for version-controlling your organization's roles, reviewing changes in pull requests, or copying a role between instances.

Unlike most content as code (which is project-scoped), custom roles are **organization-scoped**. Downloads and uploads use the `--organization` mode of the CLI and require **organization admin** permissions.

### Download roles

Run the download in organization mode from your working directory:

```bash
lightdash download --organization
```

This writes every user-defined role in your organization to `lightdash/custom-roles/*.yml`. System roles are not included.

### Upload roles

Edit or add YAML files under `lightdash/custom-roles/`, then run:

```bash
lightdash upload --organization
```

Each file is upserted by role name — the backend creates a new role if the name is unused, updates the role's description and scopes if they've drifted, or reports no change if the file already matches the server. Uploads never delete roles; remove unwanted roles in the UI.

### YAML format

```yaml
version: 1
name: analytics-viewer
description: Read-only access to analytics content
level: organization
scopes:
- view:Dashboard
- view:SavedChart
- view:Space
```

| Field | Description |
| --- | --- |
| `version` | Format version. Currently always `1`. |
| `name` | The role's display name. Used as the portable identity — a role is matched to an existing role by exact name. |
| `description` | Optional description. Set to `null` to clear an existing description. |
| `level` | Either `organization` or `project`. Determines which scopes are allowed and is immutable after the role is created. |
| `scopes` | List of scope grants. See the [scope reference](#scope-reference) above for available scopes and note that organization-only scopes cannot appear on a `level: project` role. |

Malformed files, unknown scopes, and scopes that aren't allowed at the role's level are rejected per file with the file path and error message; other files continue to process. The CLI exits with a non-zero status if any file fails validation.

For the full workflow, including examples and CI/CD integration alongside charts and dashboards, see [Custom roles as code](/guides/developer/dashboards-as-code#custom-roles-as-code).

## Troubleshooting

### Custom role doesn't restrict a permission (e.g., Google Sheets export)
Expand Down
Loading