> ## Documentation Index
> Fetch the complete documentation index at: https://lightdash-refactor-full-audit-reorg.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual views

> Turn a custom SQL query into a reusable table in Lightdash. Build a virtual view in the SQL Runner, explore and join it like any dbt table, and manage it as code.

A virtual view is created in the [SQL Runner](/explore/sql-runner) and lets other users reuse your custom SQL query in Lightdash. It gets listed in your `Tables` and can be used like the tables that come from your dbt project.

<Info>
  **A virtual view won't be saved to or managed in your dbt project.**

  If you're expecting to use this query regularly, we recommend [writing it back to dbt](/semantic-layer/dbt/write-back).
</Info>

## Create a virtual view

To create a virtual view from the SQL Runner, select the `Create Virtual View` option from the `save` drop-down.

<Frame>
  <img src="https://mintcdn.com/lightdash-refactor-full-audit-reorg/fyNjhJAU0n2GM0Ea/images/semantic-layer/virtual-views/create-virtual-view-561e25141894abbe7b7613343ea5c563.jpg?fit=max&auto=format&n=fyNjhJAU0n2GM0Ea&q=85&s=d4e0f48a0a38af040b94a9e1249dd94d" alt="" width="3061" height="1306" data-path="images/semantic-layer/virtual-views/create-virtual-view-561e25141894abbe7b7613343ea5c563.jpg" />
</Frame>

Your virtual view will appear in the list of Tables available to explore in Lightdash. Everyone in your team can use virtual views to build queries and charts just like they would any other Table.

<Frame>
  <img src="https://mintcdn.com/lightdash-refactor-full-audit-reorg/fyNjhJAU0n2GM0Ea/images/semantic-layer/virtual-views/using-virtual-views-ffc9321842862ae0f198c885aa1a9f54.jpg?fit=max&auto=format&n=fyNjhJAU0n2GM0Ea&q=85&s=9869a25e79a3fc23064b815e7051c5b8" alt="" width="3004" height="1331" data-path="images/semantic-layer/virtual-views/using-virtual-views-ffc9321842862ae0f198c885aa1a9f54.jpg" />
</Frame>

## Edit or delete a virtual view

To edit a virtual view, you need to open it in the Explorer, then beside the name, there's a three-dot-menu where you can choose to `edit` or `delete` the virtual view.

<Frame>
  <img src="https://mintcdn.com/lightdash-refactor-full-audit-reorg/fyNjhJAU0n2GM0Ea/images/semantic-layer/virtual-views/edit-delete-virtual-view-0f3ec1b5b343f2a5ca156ffe0075e658.jpg?fit=max&auto=format&n=fyNjhJAU0n2GM0Ea&q=85&s=528fe189e9d19af439bf3d38c9b7e642" alt="" width="2959" height="1351" data-path="images/semantic-layer/virtual-views/edit-delete-virtual-view-0f3ec1b5b343f2a5ca156ffe0075e658.jpg" />
</Frame>

Any changes you make will affect all existing content built using the virtual view.

## Manage virtual views as code

You can manage virtual views as code alongside your [charts and dashboards](/workflow/content-as-code) with the [Lightdash CLI](/workflow/cli/reference). 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 theme={null}
# 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 theme={null}
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 theme={null}
# 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 theme={null}
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.
