Branches & Review
Every content mutation in Contentrain Studio follows a Git-native workflow. Changes never go directly to your main branch — they are committed to short-lived feature branches and merged through a controlled pipeline. On a project with the review workflow turned on, changes can wait on their branch until a reviewer approves them.
Branch Architecture
Studio uses a three-tier branch model:
The contentrain Branch
The contentrain branch is the single source of truth (SSOT) for all Contentrain-managed content. It sits between feature branches and your main branch.
- All
cr/*branches merge intocontentrainfirst - Studio then advances your main branch to match
contentrain. This happens in the same operation as the merge, as a fast-forward, so a merge normally lands on both branches at once - If your main branch is protected, or has commits that
contentraindoes not have, Studio first tries to reconcile the two automatically. When that is not possible it opens a pull request titled "contentrain: advance content to main" and tells you "Approved, but not on your site yet: a pull request into your site's branch has to be merged first." The content is already oncontentrain; merge the pull request to bring it to your site
The sidebar shows a Branch sync line whenever contentrain and your main branch are not level, for example when content is ahead, when main has changes contentrain does not, or when the two have diverged and a pull request is open.
Feature Branches (cr/*)
Every content operation — whether triggered by the AI chat, the content panel, or the API — creates a short-lived cr/* feature branch. Branch names follow the pattern cr/{scope}/{target}[/{locale}]/{timestamp}-{suffix}:
cr/content/{modelId}/{locale}/{timestamp}-{suffix}
cr/model/{modelId}/{timestamp}-{suffix}
cr/new/init/{timestamp}-{suffix}The scope names the kind of operation: content, model, new (project setup), config (project settings such as locales and glossary terms), media, and a few others. These branches contain exactly one logical change:
- A content entry was created or updated
- A model definition was modified
- Content was copied between locales
- The project was initialized
The Review Workflow
The workflow is a project setting (Project Settings > Workflow):
| Workflow | What happens to a change |
|---|---|
| Auto-merge (default) | The change is merged into contentrain as soon as it is saved, and your main branch is advanced. The branch is deleted. |
| Review required | The change stays on its cr/* branch until the project's approval policy is met and someone with review rights approves and merges it. |
Review required needs a plan that includes the review workflow. On a plan without it, the project merges automatically regardless of the setting.
Branches waiting for review appear under Pending Changes in the project sidebar:

A pending change is listed by what it changed (the model name and locale, or a label such as "Project settings") rather than by its Git branch name. A branch a reviewer has sent back shows a changes requested badge.
What Gets Held
On a review project, a write is held when the approval policy asks for review for that kind of change. The decision depends on what is being changed, not who is changing it: an owner's edit is held by the same policy as an editor's. The same applies whether the write comes from the content editor, the status picker, the dictionary editor, the vocabulary editor, or the AI agent. When an editor or the agent saves a held change, Studio says it was saved to a branch and needs review before it goes live.
Chat tools follow the project workflow like this:
| Tool | Merge Behavior |
|---|---|
init_project | Always auto-merges |
save_content, replace_in_field, delete_content, copy_locale, update_status, vocabulary, approve_submission | Follows project workflow setting |
save_model, delete_model, add_locale | Follows project workflow setting |
merge_branch | Manual (user-initiated). On a review project it is refused until the approval policy is met |
reject_branch | Manual (user-initiated) |
request_changes | Does not merge; sends the branch back with a comment |
Reviewing Changes
To review pending changes:
- Look at the Pending Changes section in the sidebar
- Click on any branch to open the review panel
- Read what changed, then approve, request changes, or reject
The Review Panel
The review panel describes a change in the project's own terms: entries and fields, not Git files.

What the Panel Shows
- Header — the model (or kind of change), locale, who made it and when, with counts of added (
+), updated (~) and removed (-) entries - Approval card — on a review project: the risk class of the change (for example Content edit, Bulk content or Schema change), a short plan hash, who has approved, and what the policy is still waiting for
- Changes requested — the reviewer's comment, if the branch was sent back
- Structure — model changes: fields added, removed, or whose type changed, and a change of title field. A change that can take existing content with it is highlighted with "Existing content may not survive this change."
- Project settings — languages added or removed, a changed default language, a changed workflow, and glossary (vocabulary) terms added, updated or removed, with the text per locale
- Entries — grouped by model and locale. Each entry is marked added, updated or removed, and each changed field shows its before and after values. A cleared field is labeled "cleared". In a dictionary, a removed key that reappears under a new name is labeled "possibly renamed from"
- Other files — files in the change that Studio does not manage as content
Open Technical view at the bottom to see the Git view: every changed file with its status (added, modified or removed) and its content after the change.
Approvals
Approval is available on projects with the review workflow. An approval is a decision about one specific version of the change: it records who approved, their role and the exact branch tip they reviewed.

- Approve — click Approve on the approval card to record your decision
- Withdraw my approval — the same button becomes this once you have approved. You can withdraw only your own approval
- Approved — ready to merge — shown when the policy is satisfied. Until then the merge button reads Waiting for approval and is disabled
- Approved by — lists who has approved so far
- An approval stops counting when the branch changes after it was given, because a new commit is a different change. The card lists approvals that no longer count and why (for example, "plan hash mismatch" or "commit mismatch"), so you can tell why a change you approved is still blocked
- Self-approval — with no policy file, the person who made a change may approve it. A policy can forbid this (see below). The AI agent can never approve
Approving needs the same rights as merging: Reviewer, Admin or Owner. A policy can narrow it further to specific roles.
Approving does not merge by itself. Click the primary button, labeled Approve 1 change or Approve N changes, to merge. The merge is pinned to the tip you reviewed: if someone adds a commit after you approved, the merge is refused ("This branch changed after it was approved") and the panel reloads the new version for review.
Requesting Changes
Instead of approving or rejecting, a reviewer can send a branch back:
- Click Request changes at the bottom of the panel
- Write what should change before the branch can be approved
- Click Send back
The branch stays open and the comment is shown on the branch, in the review panel and in the sidebar badge. Anyone who can see the branch, usually the author, can click Mark addressed once they have dealt with it. The note is kept as history until the branch is merged or rejected. Merging or rejecting the branch clears the request. A request fires the branch.changes_requested webhook event, and the AI agent can also request changes with the request_changes tool.
Approval Policy
The approval policy is optional. It lives in your repository as .contentrain/approval-policies.json and is read from the contentrain branch. Keeping it in Git means a policy cannot be changed in a database between a change being made and being approved. The policy applies only to projects with the review workflow; an auto-merge project never consults it.
Default Behavior
With no policy file, every write above read-only needs one approval on the change before it merges, and the person who made the change may give it. This keeps a one-person project usable while still recording who approved what.
Writing a Policy
A policy sets how many approvals each kind of change needs. Changes are classified by risk, from lowest to highest: read_only, low_risk_content, bulk_content, destructive_schema, external_effect, financially_material, deployment. Studio classifies writes like this:
- Content edits are
low_risk_content - A write that touches more than one entry, any delete, copying a locale or adding a locale is
bulk_content - A single-entry edit is also treated as
bulk_contentwhen it moves the entry topublishedorarchived, empties a field, removes list items, or writes 4,000 characters of text or more - Model changes (saving or deleting a model) are
destructive_schema
{
"version": 1,
"allow_self_approval": false,
"rules": [
{ "risk": "low_risk_content", "gate": "change", "mode": "single" }
]
}| Field | Meaning |
|---|---|
version | Policy file version. Use 1 |
rules[].risk | The risk class the rule applies to. A rule also covers every class above it, except auto rules, which cover only their own class |
rules[].gate | change (the diff on a branch), plan (before the work starts), or release (before a publish or deploy) |
rules[].mode | auto (no approval needed), single (one approval), or quorum (several) |
rules[].min_approvals | For quorum: how many different approvers are needed (default 2) |
rules[].roles | Only these roles can satisfy the rule. Leave out to allow any reviewer |
rules[].models, rules[].locales | Limit the rule to specific models or locales |
default_mode | Applied at the change gate when no rule matches. Without it, a change that matches no rule needs nothing |
allow_self_approval | Set to false to require someone other than the author. The default when no policy file exists is to allow it |
To let small content edits merge on their own while still holding schema changes, trust only that class and set a default for everything else:
{
"version": 1,
"default_mode": "single",
"rules": [
{ "risk": "low_risk_content", "gate": "change", "mode": "auto" }
]
}WARNING
Without default_mode, an auto rule means a class it does not cover matches no rule at all and merges without approval. Set default_mode unless that is what you want.
If the file is not valid JSON or has an unknown value, Studio does not ignore it: the default policy applies and project health reports that the approval policy is not usable, with what is wrong.
Releases
Approving content and approving a release are separate. A manual Deploy now on a review project is refused ("This release still needs approval before it can be deployed") until the release itself has been approved under the policy. Content approvals never satisfy a release, and release approvals never satisfy a change. Release approvals are recorded by owners and admins through the API: POST /api/workspaces/{workspaceId}/projects/{projectId}/deploy/approve with an optional note, and DELETE on the same path withdraws your approval.
Approvals & Releases
On a review project, the project overview has an Approvals & releases entry. Every merge or release that went through an approval is recorded there, with who approved it, who ran it and what it covered. A project that merges without approvals records nothing here.
Merging Changes
To merge a branch:
From the UI
- Open the review panel by clicking the branch in the sidebar
- Review the changes
- On a review project, make sure the approval card reads Approved — ready to merge
- Click Approve 1 change (or Approve N changes)
- The branch is merged into
contentrainand your main branch is advanced - The feature branch is deleted
- A success toast confirms the merge ("Change merged"), or warns you if a pull request into your main branch is still needed
From the Chat
Ask the agent to merge a branch:
"Merge the branch cr/content/blog-posts/en/1774800862-27c1"Or more generally:
"Merge all pending branches"
"List branches and merge the latest one"On a review project the agent's merge follows the same approval rules as the button. It cannot approve a change itself.
Merge Conflicts
If a merge conflict occurs (another process modified the same files), Studio will report the conflict. You can resolve it by:
- Rejecting the conflicting branch
- Making the changes again (which creates a new branch with the latest base)
WARNING
Studio cannot automatically resolve merge conflicts. If a conflict is detected, the merge will fail and you will see a message indicating that manual resolution on GitHub is needed.
Rejecting Changes
To discard changes on a branch:
From the UI
- Open the review panel
- Click Reject, then click again to confirm ("Discard N changes?")
- The branch is deleted
- No changes are applied to your repository
From the Chat
"Reject the branch cr/content/blog-posts/en/1774800862-27c1"TIP
Rejecting a branch is non-destructive to your existing content. It simply discards the proposed changes by deleting the feature branch.
Branch Health
Studio monitors branch health to keep your repository clean. The count that matters is unmerged cr/* branches:
| Unmerged branches | Status | Effect |
|---|---|---|
| 0-49 | OK | Writes proceed |
| 50-79 | Warning | Writes proceed, and you are alerted |
| 80 or more | Blocked | New writes are rejected until you merge or reject existing branches |
The two limits can be changed in the project's .contentrain/config.json with branchWarnLimit and branchBlockLimit. You can also ask the AI agent for the branch health (the branch_health tool).
Branch Cleanup
Merged cr/* branches that are still around are deleted automatically once they are older than 30 days. Studio runs this check every six hours for every project; there is nothing to confirm. Owners and admins can also trigger it by hand with POST /api/workspaces/{workspaceId}/projects/{projectId}/branches/cleanup.
TIP
Regularly reviewing and merging or rejecting branches keeps your project healthy. A large number of pending branches can indicate bottlenecks in your content workflow.
Branch Naming Convention
Understanding branch names helps you identify what changed:
cr/content/blog-posts/en/1774800862-27c1
│ │ │ │ │
│ │ │ │ └─ Timestamp + suffix (unique ID)
│ │ │ └─── Locale
│ │ └────────────── Model ID (target)
│ └────────────────────── Scope (content, model, new, config, media, ...)
└───────────────────────── Contentrain prefixPermissions
Branch operations respect your role:
| Role | View Branches | Approve / Merge | Reject | Request changes |
|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | Yes |
| Editor | Yes | No | No | No |
| Reviewer | Yes | Yes | Yes | Yes |
| Viewer | Yes | No | No | No |
Mark addressed is open to anyone who can see the branch. Triggering branch cleanup by hand is limited to owners and admins. A policy can narrow who may approve with roles. The Reviewer and Viewer roles are Enterprise Edition roles; see Team Management.
Next Steps
- AI Chat — Use the agent to manage branches
- Content Editing — Make changes that create branches
- Team Management — Configure who can merge and reject