diff --git a/docs-mintlify/admin/deployment/environments.mdx b/docs-mintlify/admin/deployment/environments.mdx
index 2efbcceb9e519..ecf9eafa68a72 100644
--- a/docs-mintlify/admin/deployment/environments.mdx
+++ b/docs-mintlify/admin/deployment/environments.mdx
@@ -52,6 +52,14 @@ regardless of user activity.
+The same toggle is available in the [CLI][ref-cli] and the [Control Plane
+API][ref-control-plane-api-staging], so it can be scripted, e.g. from CI:
+
+```bash
+cube data-model enable-branch DEPLOYMENT_ID BRANCH_NAME
+cube data-model disable-branch DEPLOYMENT_ID BRANCH_NAME
+```
+
### Resources and costs
Staging environments run on Shared deployments, incurring [relevant
@@ -115,4 +123,6 @@ credentials**][ref-credentials].
[ref-overview]: /admin/connect-to-data/visualization-tools
[ref-credentials]: /admin/connect-to-data/visualization-tools
[ref-version]: /admin/deployment#cube-version
-[ref-version-channel]: /admin/deployment#update-channels
\ No newline at end of file
+[ref-version-channel]: /admin/deployment#update-channels
+[ref-cli]: /reference/cli
+[ref-control-plane-api-staging]: /reference/control-plane-api#buildapiv1deploymentsdeployment_idbranchesstaging-environment
\ No newline at end of file
diff --git a/docs-mintlify/reference/cli.mdx b/docs-mintlify/reference/cli.mdx
index 30113c5cb6cb9..79580dd27ed43 100644
--- a/docs-mintlify/reference/cli.mdx
+++ b/docs-mintlify/reference/cli.mdx
@@ -151,7 +151,7 @@ Run `cube --help` for the full options of any command.
| `logs` | Tail deployment pod logs (`--pod`, `-c/--container`, `--source production\|dev`) |
| `regions` | List available deployment regions |
| `github` (`gh`) | GitHub integration: `status`, `installations`, `repos`, `branches`, `connect` |
-| `data-model` | Data model files and Git workflow: `list`, `get`, `put`, `delete`, `rename`, `file-hashes`, `branches`, `create-branch`, `dev-mode`, `commit`, `pull`, `merge`, `merge-to-default` |
+| `data-model` | Data model files and Git workflow: `list`, `get`, `put`, `delete`, `rename`, `file-hashes`, `branches`, `create-branch`, `enable-branch`/`disable-branch`, `dev-mode`, `commit`, `pull`, `merge`, `merge-to-default` |
| `environments` | Deployment environments and environment tokens |
| `variables` | Deployment environment variables |
| `folders`, `workbooks`, `reports`, `workspace` | Workspace content management |
@@ -178,6 +178,19 @@ cube data-model merge-to-default DEPLOYMENT_ID --branch my-branch -m "add orders
`merge-to-default` merges into the deploy branch and rebuilds production.
+`enable-branch` keeps a shared branch's [staging environment][ref-staging-env]
+always active, so it stays queryable without anyone viewing the branch in the
+UI — useful for running tests against a branch from CI. `disable-branch` reverts
+to the default, where the environment is only active while viewed.
+`cube data-model branches DEPLOYMENT_ID` shows the current state per branch, and
+`cube environments list DEPLOYMENT_ID --type staging` lists the enabled ones with
+their API credentials.
+
+```bash
+cube data-model enable-branch DEPLOYMENT_ID my-branch
+cube data-model disable-branch DEPLOYMENT_ID my-branch
+```
+
## Environment variables
| Variable | Description |
@@ -200,3 +213,4 @@ or explicitly with `CUBE_NO_TELEMETRY=1` (or the legacy `CUBEJS_TELEMETRY=false`
[ref-api-keys]: /admin/account-billing/api-keys
[ref-rest-api]: /reference/core-data-apis/rest-api/index
+[ref-staging-env]: /admin/deployment/environments#staging-environments
diff --git a/docs-mintlify/reference/control-plane-api.mdx b/docs-mintlify/reference/control-plane-api.mdx
index 3b02a5264086f..66b9683849104 100644
--- a/docs-mintlify/reference/control-plane-api.mdx
+++ b/docs-mintlify/reference/control-plane-api.mdx
@@ -212,6 +212,67 @@ Example response:
```
+### `/build/api/v1/deployments/{deployment_id}/branches/staging-environment`
+
+Send a `PUT` request to enable or disable a branch, i.e. to control whether its
+[staging environment][ref-environments-staging] stays always active. Disabled
+(the default), a staging environment is only active while the branch is viewed
+in the Cube UI; enabled, it remains active and accessible regardless of user
+activity, and it is reported by
+[`/api/v1/deployments/{deployment_id}/environments`](#apiv1deploymentsdeployment_idenvironments)
+as a `staging` environment.
+
+Only shared branches can be enabled — personal development branches and the
+production branch are rejected.
+
+
+
+Note the `/build` prefix: data model and branch endpoints are served by the
+build API.
+
+
+
+Path parameters:
+
+| Parameter | Description |
+| --- | --- |
+| `deployment_id` | The deployment ID |
+
+Body parameters:
+
+| Parameter | Description | Required |
+| --- | --- | --- |
+| `enabled` | `true` enables the branch, `false` disables it | Yes |
+| `branchName` | Name of the branch to update | One of `branchName` or `branchId` |
+| `branchId` | ID of the branch to update, as returned by `GET /build/api/v1/deployments/{deployment_id}/branches` | One of `branchName` or `branchId` |
+
+Example request:
+
+```bash
+curl -X PUT \
+ -H "Authorization: Bearer YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"branchName": "staging", "enabled": true}' \
+ "https://YOUR_CUBE_CLOUD_HOST/build/api/v1/deployments/123/branches/staging-environment"
+```
+
+Example response:
+
+```json
+{
+ "data": {
+ "id": 456,
+ "name": "staging",
+ "parentBranch": "main",
+ "lastHash": "9f2c1ab",
+ "isStagingEnvironmentEnabled": true
+ }
+}
+```
+
+The same operation is available in the [CLI][ref-cli] as
+`cube data-model enable-branch` / `cube data-model disable-branch`.
+
### `/api/v1/audit-logs/export`
Send a `GET` request to export [audit log][ref-audit-log] events as a CSV
@@ -262,6 +323,8 @@ curl \
[ref-metadata-api]: /reference/core-data-apis/rest-api/reference#metadata-api
[ref-deployments]: /admin/deployment
[ref-environments]: /admin/deployment/environments
+[ref-environments-staging]: /admin/deployment/environments#staging-environments
+[ref-cli]: /reference/cli
[ref-api-keys]: /admin/account-billing/api-keys
[ref-security-context]: /docs/data-modeling/access-control/context
[ref-audit-log]: /admin/monitoring/audit-log
\ No newline at end of file
diff --git a/rust/cube-cli/README.md b/rust/cube-cli/README.md
index b5cefd81e612f..1a010a36e3a6f 100644
--- a/rust/cube-cli/README.md
+++ b/rust/cube-cli/README.md
@@ -132,7 +132,7 @@ Every endpoint of the Console Server public API is covered:
| `regions` | list available deployment regions |
| `logs` | tail deployment pod logs (`--pod`, `-c/--container`; defaults to the Cube API container) |
| `github` (`gh`) | status, installations, repos, branches, connect (import a repo into a deployment + first build) |
-| `data-model` (`dm`) | list, get, put, delete, rename files; branches, create-branch, dev-mode, exit-dev-mode, commit, pull. File writes only land on a **dev-mode branch**: `dev-mode ` forks a personal `dev-…` branch and prints its name — pass that via `--branch` (or omit `--branch` to use your active dev-mode branch); puts to any other branch are rejected by the API |
+| `data-model` (`dm`) | list, get, put, delete, rename files; branches, create-branch, enable-branch, disable-branch, dev-mode, exit-dev-mode, commit, pull. File writes only land on a **dev-mode branch**: `dev-mode ` forks a personal `dev-…` branch and prints its name — pass that via `--branch` (or omit `--branch` to use your active dev-mode branch); puts to any other branch are rejected by the API. `enable-branch` / `disable-branch` toggle whether a shared branch's staging environment stays always active (vs. only while viewed in the UI); `branches` reports it as `ENABLED` and `environments list --type staging` lists the enabled ones |
| `environments` | list, tokens, create-token (incl. `--meta-sync`) |
| `variables` | list, set (`KEY=VALUE` upserts) |
| `folders` | list, create, update, delete, ancestors |
diff --git a/rust/cube-cli/src/commands/data_model.rs b/rust/cube-cli/src/commands/data_model.rs
index 17784ccf60885..c89fb522c119b 100644
--- a/rust/cube-cli/src/commands/data_model.rs
+++ b/rust/cube-cli/src/commands/data_model.rs
@@ -4,7 +4,7 @@ use anyhow::{Context as _, Result};
use clap::Subcommand;
use serde_json::json;
-use crate::client::Query;
+use crate::client::{Client, Query};
use crate::{output, util, Ctx};
/// Manage a deployment's data model files (schema).
@@ -96,6 +96,21 @@ enum Cmd {
#[arg(long)]
dev_mode: bool,
},
+ /// Enable a branch: keep its staging environment always active
+ EnableBranch {
+ /// Deployment id
+ deployment: i64,
+ /// Branch to enable (a shared branch — not a personal dev branch)
+ branch: String,
+ },
+ /// Disable a branch: its staging environment is active only while the
+ /// branch is viewed in Cube Cloud
+ DisableBranch {
+ /// Deployment id
+ deployment: i64,
+ /// Branch to disable
+ branch: String,
+ },
/// Enter dev mode on a branch (prints the personal dev-mode branch that
/// file writes must target)
DevMode {
@@ -221,6 +236,41 @@ fn write_body(
util::body(body)
}
+/// Enabling a branch keeps its staging environment always active and accessible
+/// at `/dev-mode//cubejs-api/v1`; disabled (the default)
+/// it is only active while someone views the branch in Cube Cloud. Enabled
+/// branches are the ones `cube environments list --type staging` reports. Only
+/// shared branches qualify — personal dev branches are served as your own
+/// development environment, and the deploy branch is production.
+async fn set_branch_enabled(
+ api: &Client,
+ ctx: &Ctx,
+ deployment: i64,
+ branch: &str,
+ enabled: bool,
+) -> Result<()> {
+ let body = json!({ "branchName": branch, "enabled": enabled });
+ let res = api
+ .put(
+ &format!("/build/api/v1/deployments/{deployment}/branches/staging-environment"),
+ Some(&body),
+ )
+ .await?;
+ if ctx.json {
+ output::print_json(&res);
+ } else if enabled {
+ output::success(&format!(
+ "Enabled branch {branch}; its staging environment stays active \
+ (see `cube environments list {deployment} --type staging`)"
+ ));
+ } else {
+ output::success(&format!(
+ "Disabled branch {branch}; its staging environment is active only while viewed"
+ ));
+ }
+ Ok(())
+}
+
fn read_content(file: Option, content: Option) -> Result {
if let Some(path) = file {
return std::fs::read_to_string(&path).with_context(|| format!("failed to read {path}"));
@@ -351,8 +401,8 @@ pub async fn command(args: Args, ctx: &Ctx) -> Result<()> {
&res,
&[
("NAME", "name"),
- ("DEFAULT", "isDefault"),
- ("CURRENT", "isCurrent"),
+ ("PARENT", "parentBranch"),
+ ("ENABLED", "isStagingEnvironmentEnabled"),
],
);
}
@@ -384,6 +434,12 @@ pub async fn command(args: Args, ctx: &Ctx) -> Result<()> {
}
}
}
+ Cmd::EnableBranch { deployment, branch } => {
+ set_branch_enabled(&api, ctx, deployment, &branch, true).await?;
+ }
+ Cmd::DisableBranch { deployment, branch } => {
+ set_branch_enabled(&api, ctx, deployment, &branch, false).await?;
+ }
Cmd::DevMode { deployment, branch } => {
let body = json!({ "branchName": branch });
let res = api