Skip to content

Commit 65c92a5

Browse files
docs: document spaces-as-code access round-trip and CLI flags (#1026)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
1 parent 05924d0 commit 65c92a5

2 files changed

Lines changed: 80 additions & 11 deletions

File tree

guides/developer/dashboards-as-code.mdx

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -397,46 +397,100 @@ lightdash upload -c my-chart-slug
397397

398398
### `.space.yml` files
399399

400-
When you run `lightdash download`, the CLI automatically generates a `.space.yml` file for each space in your project. These files preserve the original human-readable name of each space so it survives a full download → edit → upload round-trip.
400+
When you run `lightdash download`, the CLI generates a `.space.yml` file for each space in your project. These files preserve the space's human-readable name **and** its direct access policy (per-user and per-group grants) so both survive a full download → edit → upload round-trip.
401401

402402
#### File format
403403

404404
```yaml
405405
contentType: space
406-
spaceName: "Data Team 🚀"
407-
slug: data-team
406+
version: 1
407+
spaceName: "Orders analytics 🚀"
408+
slug: orders-analytics
409+
access:
410+
inheritParentPermissions: false
411+
projectMemberAccessRole: viewer
412+
users:
413+
- email: alex@ecom-store.com
414+
role: admin
415+
- email: sam@ecom-store.com
416+
role: editor
417+
groups:
418+
- name: Ecom analysts
419+
role: editor
408420
```
409421

410422
| Field | Description |
411423
| --- | --- |
412424
| `contentType` | Always `space` |
425+
| `version` | Schema version. `1` opts a file into the access-aware contract described below. Legacy files without `version` and `access` remain valid metadata-only files. |
413426
| `spaceName` | The original display name of the space (emoji, casing, and special characters preserved) |
414-
| `slug` | The hyphenated slug — this matches the value used in `spaceSlug` paths |
427+
| `slug` | The full portable hierarchy path (e.g. `management/revenue`). Matches the value used in `spaceSlug` on charts and dashboards. |
428+
| `access` | Optional direct access policy. Omit to leave the current permissions untouched. |
429+
| `access.inheritParentPermissions` | Whether the space inherits access from its parent space. For root spaces this is always `false`. |
430+
| `access.projectMemberAccessRole` | Role granted to every project member (`viewer`, `editor`, `admin`), or `null` to grant no default project-wide access. |
431+
| `access.users` | Direct grants to human organization members, keyed by primary `email`. Each entry needs a `role` (`viewer`, `editor`, `admin`). Use `users: []` to explicitly clear direct user grants. |
432+
| `access.groups` | Direct grants to organization groups, keyed by exact, case-sensitive `name`. Each entry needs a `role`. Use `groups: []` to explicitly clear direct group grants. |
433+
434+
#### How access is applied on upload
435+
436+
When `access` is present, the CLI replaces the space's **complete direct policy** — inheritance, the all-project-members role, direct users, and direct groups. Empty arrays explicitly remove grants; effective inherited access and expanded group members are never serialized.
437+
438+
- **Legacy files without `access`** never mutate permissions. They still update `spaceName` and other metadata, so the round-trip is safe on projects that haven't adopted the access contract yet.
439+
- **Non-portable identities** — users or groups that can't be safely resolved on the destination (missing primary email, deleted user, ambiguous group name, SCIM-owned identity that doesn't match) — cause the space to download as a legacy metadata-only file with a destructive-change warning rather than silently revoking access on the next upload. Re-uploading that file leaves the space's access unchanged.
440+
- **Ambiguous or deleted identities** in an uploaded file produce explicit destructive warnings and cause the CLI to exit with a non-zero status rather than a silent no-op.
441+
- **Personal spaces** that Lightdash generates automatically are excluded from download and upload. **SCIM-owned users and groups** stay SCIM-owned; you cannot toggle their SCIM ownership through `.space.yml`.
442+
443+
#### Authorization
444+
445+
Replacing a space's `access` requires actual `manage:Space` on the resolved space. A broad `manage:ContentAsCode` scope on its own does not bypass restricted-space authorization. The CLI also rejects uploads that would lock the uploader out of a space they still need to manage, and serializes access changes transactionally so concurrent permission revocations can't corrupt the space's ACL.
415446

416447
#### File naming and location
417448

418-
The filename is derived from the space name: `{name-as-slug}.space.yml` (e.g. a space named "Data Team 🚀" produces `data-team.space.yml`).
449+
The filename is derived from the space name: `{name-as-slug}.space.yml` (for example, `orders-analytics.space.yml`).
450+
451+
There are three supported on-disk layouts, and `lightdash upload` discovers all of them recursively:
419452

420-
In **flat mode** (default), all `.space.yml` files are written to the root `lightdash/` directory alongside your charts and dashboards. In **nested mode** (`--nested`), each file goes into the root of its space's folder.
453+
- **Flat, under `spaces/`** — the default for new flat downloads. Every `.space.yml` file is written to `lightdash/spaces/`.
454+
- **Flat, at the root** — the legacy layout. Enable it on new downloads with `--root-spaces`.
455+
- **Nested** — with `--nested`, each `.space.yml` file lives inside its own space folder alongside that space's `charts/` and `dashboards/` directories.
456+
457+
Existing files are always updated in their current location, so re-running `lightdash download` never duplicates or automatically moves root-level, `spaces/`, or nested files.
421458

422459
Example flat layout after `lightdash download`:
423460

424461
```text
425462
lightdash/
426-
├── data-team.space.yml
427-
├── marketing.space.yml
463+
├── spaces/
464+
│ ├── orders-analytics.space.yml
465+
│ └── marketing.space.yml
428466
├── charts/
429467
│ └── monthly-revenue.yml
430468
└── dashboards/
431469
└── executive-summary.yml
432470
```
433471

434-
#### How upload uses `.space.yml` files
472+
#### Upload ordering
473+
474+
`lightdash upload` processes `.space.yml` files **before** dependent content (charts, dashboards, scheduled deliveries, and so on) so that a chart's target space always exists and has its final ACL before the chart is written. Parents in a nested hierarchy are processed before their descendants; missing spaces are created unless you pass `--skip-space-create`.
475+
476+
If you deploy organization-scoped content alongside project content, keep this order:
477+
478+
1. `lightdash upload --organization`
479+
2. `lightdash upload --spaces-only` (or a full upload) to reconcile spaces
480+
3. Project content (bare `lightdash upload`, or one filtered by `--charts` / `--dashboards`)
481+
482+
#### Controlling what runs
483+
484+
Full project downloads and uploads include spaces by default. Use:
485+
486+
- `--skip-spaces` — skip spaces entirely on the current run. Useful when you only want to reconcile charts and dashboards.
487+
- `--spaces-only` — download or upload only `.space.yml` files. Handy for permission-only PRs and organization promotions.
488+
- `--root-spaces` — write new flat downloads at the `lightdash/` root instead of `lightdash/spaces/` (download-only; not combinable with `--nested`).
435489

436-
During `lightdash upload`, the CLI scans for all `.space.yml` files and builds a `slug → spaceName` mapping. This is passed to the backend so that when spaces are auto-created, they use the original `spaceName` rather than deriving a name from the slug. If no `.space.yml` is found for a slug, the CLI falls back to generating a friendly name from the slug.
490+
Filtered chart and dashboard operations (`-c`, `-d`) never reconcile unrelated space access. They still maintain metadata-only `.space.yml` files for the referenced spaces so names stay portable; an existing versioned `access` block is preserved rather than replaced by embedded metadata.
437491

438492
<Note>
439-
`.space.yml` files currently store name metadata only (`spaceName` and `slug`). Access control properties (`isPrivate`, group access) are not yet supported in these files.
493+
If the access-aware space endpoint is unavailable on the destination, a bare `lightdash download` falls back to legacy metadata-only `.space.yml` files embedded in chart and dashboard responses. `--spaces-only` remains strict — it has no content response to fall back on and will surface the error.
440494
</Note>
441495

442496
## Virtual views

references/lightdash-cli.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,15 @@ You can make changes to the code and upload these changes back to your Lightdash
687687
- `--nested`
688688
- (default: false)
689689
- split downloaded content into sub-folders matching the spaces in your project
690+
- `--skip-spaces`
691+
- (default: false)
692+
- skip downloading [`.space.yml` files](/guides/developer/dashboards-as-code#space-yml-files) (space metadata and access policies). Use this when you only want to reconcile charts and dashboards.
693+
- `--spaces-only`
694+
- (default: false)
695+
- download only [`.space.yml` files](/guides/developer/dashboards-as-code#space-yml-files) — no charts or dashboards. Useful for permission-only PRs and cross-instance access promotions. Strict: has no chart/dashboard fallback if the access-aware space endpoint is unavailable.
696+
- `--root-spaces`
697+
- (default: false)
698+
- write new flat `.space.yml` downloads at the `lightdash/` root instead of `lightdash/spaces/` (legacy layout). Existing files are always updated in place regardless of this flag. Cannot be combined with `--nested`.
690699
- `--strip-pivot-series`
691700
- (default: false)
692701
- 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.
@@ -819,6 +828,12 @@ If there have been changes made to a chart or dashboard in the application that
819828
- `--skip-space-create`
820829
- (default: false)
821830
- Skip space creation if it does not exist
831+
- `--skip-spaces`
832+
- (default: false)
833+
- skip uploading [`.space.yml` files](/guides/developer/dashboards-as-code#space-yml-files). Charts and dashboards still upload as normal, and existing space permissions are left untouched.
834+
- `--spaces-only`
835+
- (default: false)
836+
- upload only [`.space.yml` files](/guides/developer/dashboards-as-code#space-yml-files) — no charts or dashboards. Spaces are always uploaded before dependent content, so this is the right flag for a permissions-only promotion between environments. Replacing a space's `access` policy requires actual `manage:Space` on the resolved space; broad `manage:ContentAsCode` alone doesn't bypass restricted-space authorization.
822837
- `--include-charts`
823838
- (default: false)
824839
- Include charts updates when uploading dashboards

0 commit comments

Comments
 (0)