You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
401
401
402
402
#### File format
403
403
404
404
```yaml
405
405
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
408
420
```
409
421
410
422
| Field | Description |
411
423
| --- | --- |
412
424
| `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. |
413
426
| `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.
415
446
416
447
#### File naming and location
417
448
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:
419
452
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.
421
458
422
459
Example flat layout after `lightdash download`:
423
460
424
461
```text
425
462
lightdash/
426
-
├── data-team.space.yml
427
-
├── marketing.space.yml
463
+
├── spaces/
464
+
│ ├── orders-analytics.space.yml
465
+
│ └── marketing.space.yml
428
466
├── charts/
429
467
│ └── monthly-revenue.yml
430
468
└── dashboards/
431
469
└── executive-summary.yml
432
470
```
433
471
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`).
435
489
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.
437
491
438
492
<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.
Copy file name to clipboardExpand all lines: references/lightdash-cli.mdx
+15Lines changed: 15 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -687,6 +687,15 @@ You can make changes to the code and upload these changes back to your Lightdash
687
687
-`--nested`
688
688
- (default: false)
689
689
- 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`.
690
699
-`--strip-pivot-series`
691
700
- (default: false)
692
701
- 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
819
828
- `--skip-space-create`
820
829
- (default: false)
821
830
- 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.
822
837
- `--include-charts`
823
838
- (default: false)
824
839
- Include charts updates when uploading dashboards
0 commit comments