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
116 changes: 116 additions & 0 deletions guides/developer/dashboards-as-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,122 @@ During `lightdash upload`, the CLI scans for all `.space.yml` files and builds a
`.space.yml` files currently store name metadata only (`spaceName` and `slug`). Access control properties (`isPrivate`, group access) are not yet supported in these files.
</Note>

## Virtual views

You can manage [virtual views](/guides/developer/virtual-views) as code alongside your charts and dashboards. This is useful when you want to review virtual-view changes in a pull request, promote the same SQL across projects (for example from a staging to a production Lightdash instance), or template a set of virtual views for reuse.

Virtual views are **opt-in** — a bare `lightdash download` does not pull them, and a bare `lightdash upload` does not push them. You have to ask for them explicitly with `--include-virtual-views` or `--virtual-views <slug>`.

### Download virtual views

Use `--include-virtual-views` to download every virtual view in the project, or `--virtual-views <slug>` to download specific ones:

```bash
# All virtual views in the project
lightdash download --include-virtual-views

# Only the virtual views you name
lightdash download --virtual-views orders_enriched customers_enriched
```

Each virtual view is written to `lightdash/virtual-views/<slug>.yml`. To skip virtual views when a filter is otherwise selecting them, add `--skip-virtual-views`.

### Virtual view YAML

Virtual views serialize to a portable YAML contract keyed by a project-scoped `slug` (the immutable explore name used by any charts, dashboards, or joined models that reference the view). The `name` is the mutable display label shown in the UI.

```yml Virtual view YAML example
contentType: virtual_view
version: 1
slug: orders_enriched
name: "Orders enriched"
sql: |-
select
o.order_id,
o.customer_id,
o.status,
o.created_at,
o.total_amount,
c.country
from ecom.orders o
left join ecom.customers c on c.customer_id = o.customer_id
columns:
- reference: country
type: string
- reference: created_at
type: timestamp
- reference: customer_id
type: string
- reference: order_id
type: string
- reference: status
type: string
- reference: total_amount
type: number
parameters: null
```

| Field | Description |
| --- | --- |
| `contentType` | Always `virtual_view`. |
| `version` | Schema version. Currently `1`. |
| `slug` | Immutable project-scoped identifier. Must be canonical `snake_case` — this is the explore name that downstream charts and dashboards reference. |
| `name` | Display label for the UI. Safe to rename. |
| `sql` | Raw SELECT for the virtual view. Referenced parameters must have matching keys in `parameters`. |
| `columns` | The list of columns exposed as dimensions. Each entry needs a unique `reference` and a valid dimension `type`. Columns are sorted by reference on download. |
| `parameters` | Saved parameter values used by the view's SQL, or `null` if the view takes no parameters. |

### Upload virtual views

Use `--virtual-views <slug>` on `lightdash upload` to target specific views, or run a bare upload (without any content filters) to include every virtual view in `lightdash/virtual-views/`.

```bash
# Upload one virtual view
lightdash upload --virtual-views orders_enriched

# Upload everything, including any virtual views on disk
lightdash upload
```

The CLI compares each file against the current cached version of the view and reports one of:

- **created** — no view with this slug existed yet.
- **updated** — the view existed and one or more fields changed.
- **skipped** — the file is byte-equivalent to the current view, so nothing is sent.

If the same slug already exists as a **non-virtual** explore (for example, a dbt model), the upload is rejected — Lightdash won't adopt a dbt-managed explore into virtual-view storage.

### Validation errors

Uploads fail fast with a `ParameterError` when the YAML isn't a valid virtual view. Common causes:

- `slug` is empty, contains slashes, or isn't canonical `snake_case`.
- `name` or `sql` is empty.
- `columns` is empty, contains duplicate or blank `reference` values, or uses an unknown `type`.
- `parameters` includes keys that the `sql` never references.
- `contentType` isn't `virtual_view` or `version` isn't the supported version.

Fix the YAML locally and re-run `lightdash upload`.

### Destructive changes and `--force`

Removing a column or changing its type is a **destructive** change — any charts or dashboards that reference the removed or retyped column will break. To protect against accidental breakage, the CLI rejects destructive column changes by default and lists the offending columns in the error.

Re-run the upload with `--force` when the change is intentional:

```bash
lightdash upload --virtual-views orders_enriched --force
```

`--force` also allows replacing a virtual view whose cached state can no longer be represented as YAML (for example, a legacy view with non-subquery SQL).

### Permissions

Virtual view download and upload reuse the same content-as-code scopes as charts and dashboards:

- `view:ContentAsCode` is required to download virtual views.
- `manage:ContentAsCode` is required to upload virtual views. Uploads also check the virtual-view edit permissions the UI enforces, so the CLI, API, and app stay in lockstep.

## Dashboard as code yml reference

The yml configuration for dashboards as code is extensive. It covers both dashboards and individual charts. The best way to start is often to create your content in the Lightdash UI, download it, and in most cases the structure will be fairly intuitive. Below are outlines of the structures you will find for both types of content to provide some additional context.
Expand Down
34 changes: 33 additions & 1 deletion references/lightdash-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,11 @@ You can make changes to the code and upload these changes back to your Lightdash
- `--strip-pivot-series`
- (default: false)
- strip per-value pivot series config from chart YAML so it's portable across projects. Cartesian chart series that are tied to specific pivot field values are rewritten to reference only the underlying x/y fields, and duplicates collapsed into a single entry. Use this when the source project's pivot values don't exist in the target project (for example, when reusing chart YAML as a template), so the upload doesn't fail or render with the original project's hardcoded series. Only applies to regular charts; SQL charts and dashboards are unaffected.
- `--virtual-views <slugs...>`
- opt-in flag to download specific [virtual views](/guides/developer/dashboards-as-code#virtual-views) as code. Pass one or more virtual-view slugs. Each view is written to `lightdash/virtual-views/<slug>.yml`. Virtual views are not included in a bare `lightdash download` — use this flag or `--include-virtual-views` to opt in.
- `--include-virtual-views`
- (default: false)
- opt-in flag to download every [virtual view](/guides/developer/dashboards-as-code#virtual-views) in the project as code. Use `--virtual-views <slugs...>` instead when you only want specific views.
- `--apps <appUuids...>`
- opt-in flag to also download specific [data apps](/guides/data-apps#data-apps-as-code) as code (enterprise only). Pass one or more app UUIDs to download just those apps — this is the only way to download apps that haven't been added to a space. Each app is written to `lightdash/apps/<slug>/` as a locally buildable project: `lightdash-app.yml` manifest, the app's `src/` tree, `package.json`, a read-only `.lightdash/context/` snapshot of the project's semantic layer, and agent skill files. The built output is not included — the server rebuilds it on upload.
- `--include-apps`
Expand Down Expand Up @@ -772,6 +777,16 @@ Download just one data app and nothing else.
lightdash download --apps-only --apps 8f2b1c4d-1111-2222-3333-444455556666
```

Download all charts and dashboards, plus every virtual view in the project.

```bash
lightdash download --include-virtual-views
```

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).

```bash
Expand All @@ -792,7 +807,7 @@ If there have been changes made to a chart or dashboard in the application that
**Options:**

- `--force`
- if you've created new content as code that doesn't exist in your Lightdash project yet, you need to run `lightdash upload --force` to create the new charts and dashboards—otherwise, `lightdash upload` only uploads updates to existing content
- required to create new content as code that doesn't exist in your Lightdash project yet — without it, `lightdash upload` only updates existing content. Also required to apply destructive [virtual view](/guides/developer/dashboards-as-code#virtual-views) column changes (removing a column or changing its type).
- `--charts` or `-c`
- select specific charts as code to upload back to your project. Use the chart SLUG to specify the chart
- `--dashboards` or `-d`
Expand All @@ -816,6 +831,11 @@ If there have been changes made to a chart or dashboard in the application that
- `--nested`
- (default: false)
- expect content to be organized in sub-folders matching the spaces in your project (use this if content was downloaded with `--nested`)
- `--virtual-views <slugs...>`
- specify [virtual view](/guides/developer/dashboards-as-code#virtual-views) slugs to upload from `lightdash/virtual-views/`. Combine with `--force` to apply destructive column changes.
- `--skip-virtual-views`
- (default: false)
- skip uploading virtual views, even when `lightdash/virtual-views/` contains files
- `--apps [appUuids...]`
- opt-in flag to also upload [data apps](/guides/data-apps#data-apps-as-code) as code (enterprise only). Bare `--apps` uploads every app folder under `lightdash/apps/`. Pass one or more app UUIDs to upload just those apps. The CLI posts each app's `src/` tree and returns immediately — the destination rebuilds the app in its sandbox and publishes a new version. The app shows a `building` status in the UI until the build finishes. Uploading to the same project appends a new version and applies any edits to the manifest's `name` or `description`; uploading to a different project prompts for confirmation (in non-interactive shells the app is skipped with an error unless you also pass `--create-new`) and creates a new app in the target. After any create, the CLI prints the new app's URL and offers to retarget the local `lightdash-app.yml` (updating `appUuid`, `projectUuid`, and `version`) so future uploads update the new app.
- `--create-new`
Expand Down Expand Up @@ -871,6 +891,18 @@ Upload all charts and dashboards and validate them after upload to check for err
lightdash upload --validate
```

Upload a specific virtual view.

```bash
lightdash upload --virtual-views orders_enriched
```

Apply a destructive virtual view change (removing a column or changing its type) — the CLI blocks this unless `--force` is passed.

```bash
lightdash upload --virtual-views orders_enriched --force
```

Upload all charts, dashboards, and data app folders under `lightdash/apps/`.

```bash
Expand Down
Loading