Skip to content
Open
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
183 changes: 165 additions & 18 deletions guides/developer/dashboards-as-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -899,17 +899,39 @@ And the matching tile entry on a dashboard:
tileSlug: orders-by-status
```

## Custom roles as code
## Organization content 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.
Three organization-scoped resources — **custom roles**, **users**, and **groups** — can also be downloaded and uploaded as YAML using the `--organization` mode of `lightdash download` and `lightdash upload`. This is a separate workflow from project content (charts, dashboards, virtual views, and data apps) and requires an **organization admin**.

### When to use it
A single `lightdash download --organization` writes every organization resource to a top-level folder in your working directory:

```
lightdash/
custom-roles/
<role-slug>.yml
users/
<email-slug>.yml
groups/
<group-slug>.yml
```

A single `lightdash upload --organization` reads all three folders and applies them **sequentially in dependency order**: custom roles first, then users, then groups. A failed phase prevents the dependent phases from running, so a user file can safely reference a custom role added in the same commit, and a group file can safely list a user added in the same commit.

<Note>
Organization mode cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, `--nested`, or `--force`. Uploads never delete resources in Lightdash — remove unwanted roles, users, or groups in the UI.
</Note>

### Custom roles as code

[Custom roles](/references/workspace/custom-roles) are 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
#### YAML contract

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

Expand All @@ -932,26 +954,17 @@ scopes:
| `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
The filename is derived from the role name; the CLI creates safe filenames and disambiguates collisions automatically. A missing or empty `custom-roles/` directory is a no-op.

Run the download in `--organization` mode from your working directory:
#### Download and upload

```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
```

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

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.
Expand All @@ -964,14 +977,148 @@ Uploads are per-file. A malformed or invalid file is reported with its file path
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
#### 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.

### Users as code

Organization users can be downloaded and uploaded as portable YAML records. Use this to version-control who has access to your organization, review membership changes in pull requests, or replicate a set of users from staging to production. Users as code sits alongside custom roles under the `--organization` workflow and requires an organization admin.

Credentials are **never** portable. Users as code does not carry passwords, SSO tokens, or session state — users continue to authenticate through your instance's existing password, SSO, or invitation flows. Uploading a user record only reconciles their organization role and active/disabled state; it never adds or removes an authentication method.

#### When to use it

- Version-control organization membership and role assignments.
- Promote a curated user list from a staging instance to production.
- Review role changes (for example, promoting someone to admin) in a pull request before applying them.

#### YAML contract

Each user serializes to a single file under `lightdash/users/<email-slug>.yml`, keyed by their normalized (lowercased) primary email:

```yaml
version: 1
email: alex@ecom-store.com
disabled: false
role:
type: system
name: editor
```

To assign a custom role, reference it by its exact organization-level name:

```yaml
version: 1
email: taylor@ecom-store.com
disabled: false
role:
type: custom
name: analytics-viewer
```

| Field | Description |
| --- | --- |
| `version` | Format version. Currently always `1`. |
| `email` | The user's primary email address. Normalized to lowercase and used as the portable identity — a user is matched to an existing member by exact email within the organization. |
| `disabled` | `true` to deactivate the member, `false` to keep them active. Uploads never disable the last enabled authenticated admin. |
| `role.type` | Either `system` or `custom`. `system` uses one of the built-in roles (`member`, `viewer`, `interactive_viewer`, `editor`, `developer`, `admin`); `custom` references a user-defined role. |
| `role.name` | For `system`, the built-in role name. For `custom`, the exact organization-level custom role name — the referenced role must exist (or be created in the same upload's custom-roles phase). |

Lifecycle status (whether a user has authenticated yet, whether they have a valid invitation) is **derived from the destination user during upload** and is not declared in YAML. Legacy fields such as `pending` are rejected.

#### Download and upload

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

Download writes every organization member to `lightdash/users/*.yml`. Uploads reconcile each file:

- **Created** — no member with that email exists, so the member is added and staged as awaiting authentication.
- **Updated** — the member exists and their role or `disabled` state has changed.
- **Unchanged** — the member exists and already matches the file.

Omitting a user file does not remove the member from your organization — deactivate or delete users in the UI. Custom-role references are resolved during the users phase after custom roles have been applied, so a user file can safely reference a role added in the same commit.

#### Sending invitations

Invitations are a separate, opt-in side effect. Users authenticate through your instance's existing domain, SSO, or manually triggered invitation flows by default. To send an invitation email as part of an upload, add `--send-invites`:

```bash
lightdash upload --organization --send-invites
```

`--send-invites` only sends invitations to eligible staged users. Authenticated users, disabled users, and users with a still-valid invitation are skipped.

#### Permissions and safety

- The user running organization download and upload must be an **organization admin**.
- Uploads enforce the **organization boundary** — a user file cannot move a member into a different organization.
- Uploads preserve the **admin invariant**. Promotions to admin are processed before demotions and disables, and the backend rejects any individual operation that would leave the organization without an enabled authenticated admin.

### Groups as code

Organization groups can be downloaded and uploaded as portable YAML records keyed by the exact, case-sensitive group name. Use this to version-control group membership and copy groups between instances. Groups as code sits alongside custom roles and users under the `--organization` workflow and requires an organization admin.

#### When to use it

- Version-control group membership alongside custom roles and users.
- Promote a group configuration from staging to production.
- Review membership changes in a pull request before they are applied.

#### YAML contract

Each group serializes to a single file under `lightdash/groups/<group-slug>.yml`:

```yaml
version: 1
name: Ecom Finance
members:
- alex@ecom-store.com
- taylor@ecom-store.com
```

An empty group is represented explicitly as `members: []`.

| Field | Description |
| --- | --- |
| `version` | Format version. Currently always `1`. |
| `name` | The group's exact display name. This is the portable identity — a group is matched to an existing group by exact, case-sensitive name. Renaming a group in YAML creates a new group; the original group is left intact. |
| `members` | List of primary email addresses. Every email must resolve to an existing, non-internal user in the destination organization before any mutation begins. |

Group files intentionally exclude UUIDs, project roles, space access, AI-agent access, user attributes, and ownership metadata — those continue to be managed in the UI.

#### Download and upload

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

Download writes every group and its membership to `lightdash/groups/*.yml`. Uploads reconcile each file:

- **Created** — no group with that name exists, so a new group is created with the listed members.
- **Updated** — the group exists and its membership set differs; the complete membership is replaced atomically.
- **Unchanged** — the group exists and its membership already matches the file.

If any listed email cannot be resolved to a user in the destination organization, the group's mutation is rejected atomically — membership is never partially applied. Missing files do not delete groups; remove unwanted groups in the UI.

Because groups run after the users phase, a group file can safely list a user added in the same commit.

<Note>
**Groups feature disabled.** If the groups feature is turned off on the destination instance, `lightdash download --organization` skips the groups folder. Run with `--verbose` to see an explanation.
</Note>

<Note>
**SCIM-managed groups.** Groups do not yet record management provenance, so content as code and SCIM should not manage the same group. If a group is provisioned by SCIM, keep managing it through SCIM and don't add a matching file under `lightdash/groups/`.
</Note>

## 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
17 changes: 13 additions & 4 deletions references/lightdash-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ You can make changes to the code and upload these changes back to your Lightdash
- 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`.
- switch to organization mode and download [organization-scoped content](/guides/developer/dashboards-as-code#organization-content-as-code) — [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code), [users](/guides/developer/dashboards-as-code#users-as-code), and [groups](/guides/developer/dashboards-as-code#groups-as-code) — instead of project content. Files are written to `lightdash/custom-roles/<role-slug>.yml`, `lightdash/users/<email-slug>.yml`, and `lightdash/groups/<group-slug>.yml`. Requires organization admin permissions. If the groups feature is disabled on the instance, the groups folder is skipped (run with `--verbose` for the explanation). Cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, or `--nested`.

**Examples:**

Expand Down Expand Up @@ -787,7 +787,7 @@ Download only specific virtual views by slug.

```bash
lightdash download --virtual-views orders_enriched customers_enriched
Download every organization-scoped custom role as code (requires org admin).
Download every [organization-scoped resource](/guides/developer/dashboards-as-code#organization-content-as-code) as code — custom roles, users, and groups (requires org admin).

```bash
lightdash download --organization
Expand Down Expand Up @@ -843,7 +843,10 @@ If there have been changes made to a chart or dashboard in the application that
- 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.
- switch to organization mode and upload [organization-scoped content](/guides/developer/dashboards-as-code#organization-content-as-code) — [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code), [users](/guides/developer/dashboards-as-code#users-as-code), and [groups](/guides/developer/dashboards-as-code#groups-as-code) — instead of project content. Reads YAML files from `lightdash/custom-roles/`, `lightdash/users/`, and `lightdash/groups/` and applies them sequentially in that order (custom roles → users → groups), so a user file can reference a custom role and a group file can reference a user from the same upload. Each resource is upserted by its portable identity (role name, primary email, group 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, users, or groups.
- `--send-invites`
- (default: false)
- only valid with `--organization`. When uploading users, send invitation emails to eligible staged users. Users who have already authenticated, are disabled, or already have a valid outstanding invitation are skipped. Without this flag, users authenticate through the instance's existing domain, SSO, or manually triggered invitation flows — credentials are never carried in the YAML.

**Examples:**

Expand Down Expand Up @@ -921,12 +924,18 @@ 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).
Upload every [organization-scoped resource](/guides/developer/dashboards-as-code#organization-content-as-code) — custom roles, users, and groups — back to the organization (requires org admin).

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

Upload organization resources and also send invitation emails to any staged users who need to authenticate.

```bash
lightdash upload --organization --send-invites
```


### `lightdash rename`

Expand Down
2 changes: 1 addition & 1 deletion references/workspace/custom-roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ scopes:

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).
Organization users and groups can be managed through the same `--organization` workflow, and uploads apply resources sequentially — custom roles first, then users, then groups — so a user file can safely reference a custom role added in the same commit. See [Organization content as code](/guides/developer/dashboards-as-code#organization-content-as-code) for the full workflow, [Users as code](/guides/developer/dashboards-as-code#users-as-code), and [Groups as code](/guides/developer/dashboards-as-code#groups-as-code).

## Troubleshooting

Expand Down
Loading