About netbox-changes
An operational reference for people managing change requests with the NetBox Changes plugin. It explains request lifecycles, review policies, and branch merge gating, with attention to API workflows and status rules.
Key features
- 01Change request lifecycle management
- 02Reviews and policy rules
- 03Automatic status transitions
- 04Branch merge gating
- 05Main-branch protection settings
Use cases
- 01Creating and submitting a branch change request
- 02Setting reviewer requirements and eligible reviewers
- 03Tracking review feedback and resubmitting changes
- 04Requiring approval before merging
MCP support
A NetBox MCP server can query existing change requests. NetBox Platform MCP can support creating, reviewing, and approving requests.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ netboxlabs/ skills --skill netbox-changesAfter installing, type/netbox-changesin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-patterns.mdcr-lifecycle.mdpolicy-system.mdprotect-main.mdDetails
From the project READMENetBox Changes — Change Request Management
Your knowledge of NetBox Changes may be outdated. CR lifecycle, policy enforcement, and API behavior evolve between plugin releases. Prefer retrieval over pre-trained knowledge.
Retrieval Sources
| Source | URL / Method | Use for |
|---|---|---|
| Changes docs | https://netboxlabs.com/docs/developer/plugins-extensions/changes/ | Configuration, models, lifecycle |
| Changes models | https://netboxlabs.com/docs/developer/plugins-extensions/changes/models/changerequest/ | CR fields and states |
| NetBox MCP server | If configured — query existing change requests | Live CR state |
| NetBox Platform MCP | If configured — full CR lifecycle operations | Create, review, approve CRs |
FIRST: Verify Connectivity
Confirm the Changes plugin is installed:
curl -s -H "Authorization: Bearer $NETBOX_TOKEN" "$NETBOX_URL/api/plugins/changes/change-requests/" | python -m json.tool
If you get 404, the netbox_changes plugin is not installed. If 403, your token lacks permissions.
The netbox_changes plugin adds a code-review-style workflow on top of NetBox Branching. Every branch can have one Change Request (CR) that gates merge via policies and reviews.
Plugin: netbox_changes 1.1.x (latest v1.1.3, 2026-09-09) · NetBox: 4.5.2–4.7 (1.0.2 raised the floor from 4.4 to 4.5.2; 1.1.3 added 4.7) · Base URL: /api/plugins/changes/
Pairing with branching: Changes requires netbox-branching at the line matching your NetBox — branching 1.1.x on NetBox 4.5.2–4.6, branching 1.2.x on NetBox 4.7. A mismatch prevents NetBox from starting. See netbox-branching.
Plugin v1.0+ changed CR lifecycle semantics vs the 0.4.x line: a branch may hold multiple CRs (one active at a time), rejected CRs can be reopened/replaced,
policyis required, andchanges-requestedis a settable status under an unmet policy. v1.1.0 added per-policyis_defaultandrequire_independent_review. Flagged inline below; see Version Notes.
Quick Reference
Endpoints
| Path | Purpose |
|---|---|
change-requests/ | CR CRUD and status management |
reviews/ | Submit / list reviews |
policies/ | Review policies |
policy-rules/ | Rules within policies |
comments/ | CR comments |
comment-replies/ | Threaded replies |
change-requests/<id>/retrigger/ (POST) | Re-emit event rules for a CR (event_retriggered); needs change permission and a write-enabled token |
CR Statuses (exact values)
draft · needs-review · changes-requested · approved · completed · rejected
Review Statuses
pending · comment · changes-requested · approved · rejected
pending is a "review requested, not yet submitted" placeholder — never POST it. The review form hides it (1.1.1+) and the REST API rejects it: pending is not a valid status for a submitted review.
Core Concepts
One Active CR Per Branch
Plugin v1.0+: a branch may have multiple change requests over time, but only one active at a time (any status except
completedorrejected). Creating a second active CR for a branch fails with aValidationError:"This branch already has an active change request."Once a CR isrejectedorcompleted, the slot frees up — you can open a fresh CR (or reopen the rejected one) for the same branch. Branch deletion cascades to CR deletion.(In 0.4.x the branch↔CR relation was strictly one-to-one and a second CR raised an
IntegrityError— do not rely on that against v1.0+.)
Author/Owner Fields Are Read-Only
ChangeRequest.owner, Review.user, Comment.author, CommentReply.author
are all set automatically to the current user. Never send them in POST
bodies — they'll be ignored.
CR Lifecycle
State Machine
CREATE ──→ draft ──→ needs-review ←─────────────────┐
│ ↑ │
│ │ (new changes or │
│ │ policy rule change) │
▼ │ │
changes-requested ──────────────────┘
│
▼
approved
│
[branch merge]
│
▼
completed
Any active status ──→ rejected (manual)
New CRs can only be created as draft or needs-review.
Automatic Status Transitions
Most transitions happen automatically:
| Trigger | From → To |
|---|---|
| Review submitted, policy now met | needs-review/changes-requested → approved |
Review with changes-requested | any active → changes-requested |
| New changes made in branch | approved/changes-requested → needs-review |
| Branch merged | approved → completed (requires merge to complete successfully with actual changes) |
| Policy rule changed or deleted, policy no longer met | approved → needs-review |
Manual transitions: Users can set draft, needs-review, changes-requested,
or rejected directly while the policy is unmet (the valid manual set is
draft / needs-review / changes-requested / rejected). You cannot
manually set approved — it's only reachable when the policy passes.
completed is only set automatically on merge. A reviewer submitting a
"changes-requested" review also moves the CR to changes-requested.
Plugin v1.0+:
changes-requestedis now a valid manual status under an unmet policy (in 0.4.x it was auto-only). ArejectedCR is no longer a dead end — it can be reopened (set back todraft/needs-review) or replaced by a new CR on the same branch.
See references/cr-lifecycle.md for the complete transition table.
Creating a Change Request
POST /api/plugins/changes/change-requests/
{
"name": "Add new switches",
"branch": 123,
"policy": 1,
"status": "needs-review",
"priority": 3,
"summary": "Adding new switches to DC1"
}
owneris set automatically — do NOT include itbranchis the branch PK (must exist, must not already have an active CR)policyis required (v1.0+) — a non-null FK on every CR; a POST withoutpolicyfails. (It also drives merge gating.)- (1.1.0+) the policy flagged
is_default: true(at most one) is pre-selected in the UI form only — the REST API does not fill it in. Find it withGET /api/plugins/changes/policies/?is_default=trueand pass itsid. priorityis an integer (1=low, 5=high)
Review Workflow
Submitting a Review
POST /api/plugins/changes/reviews/
{
"change_request": 1,
"status": "approved",
"comments": "LGTM"
}
The user field is set automatically to the current user. Do not send it.
Stale Review Tracking
Reviews track which changes they've seen. If new changes are made after a review, that review becomes stale and may need to be re-submitted.
- Stale reviews don't count toward policy evaluation
- Only the latest non-stale review per user is considered
- Making changes after approval → CR reverts to
needs-review, reviewers must re-approve
Review Iteration Pattern
- Reviewer submits
changes-requested→ CR status becomeschanges-requested - Author makes fixes in branch → CR reverts to
needs-review(stale approvals invalidated) - Reviewer re-reviews and submits
approved→ if policy met, CR becomesapproved
Policy System
A Policy contains one or more PolicyRules. ALL enabled rules must pass for the policy to be satisfied.
Policy Fields (1.1.0+)
is_default— pre-selected on the UI CR form; at most one policy (validation errorA default policy already exists: <name>.). No effect on the REST API.require_independent_review— the CR owner's own reviews never count toward this policy's rules. Owner can still post reviews (they're recorded, just excluded). If the owner is a rule's only eligible reviewer, the rule is unsatisfiable for their CRs. Defaultfalse(owner reviews count, as in 1.0.x).
PolicyRule Fields
min_reviews— minimum approved (non-stale) reviews required, 0–10.0makes the rule always pass (not recommended).reviewers— M2M to specific Usersreviewer_groups— M2M to Groups- Only reviews from users in
reviewers∪reviewer_groupsmembers count enabled— disabled rules are skipped
Critical Gotcha: Zero Rules = Always Fails
A Policy with zero enabled rules always returns False. You must create at
least one enabled PolicyRule for a CR to ever reach approved.
See references/policy-system.md for setup examples and evaluation details.
Merge Gating
Merge gating is always active when the plugin is installed — there's no configuration toggle. Every branch merge requires an approved CR.
If you try to merge a branch without an approved CR, the UI reports
No change request has been approved for this branch. Via REST, the branching
merge/ endpoint still returns 200 + Job; the job then errors with
Merging this branch is not permitted. — poll the job, don't trust the 200.
Dry-run merges ("commit": false) bypass the gate.
protect_main
Disabled by default. When enabled, direct writes to branch-aware models are blocked unless the user has the bypass permission. All changes must go through a branch.
# netbox config
PLUGINS_CONFIG = {
'netbox_changes': {
'protect_main': True,
}
}
- Bypass permission (v1.0+): grant the
bypasscustom action on the Policy object (NetBox Change Management → Policy in the ObjectPermission form). Superusers get it implicitly. Describe it as "the bypass action on Policy" rather than a single flat permission string — internally the codename isbypasswhile the enforcement check still referencesbypass_policy. - During branch merge/revert, protect_main is temporarily suspended
- Error when blocked:
"Changes directly to main are not permitted." - (NetBox 4.7.0–4.7.2)
?background=truebulk writes run in a worker with no active branch even when sent withX-NetBox-Branch(the header isn't carried to the job), so protect_main rejects them with the error above — inspect the job, the request itself returns 202. Use synchronous bulk writes in branch context there. (NetBox 4.7.3+) the header and query string are carried into the job, so background bulk writes land in the branch.
See references/protect-main.md for details.
Complete Workflow
- Create branch (via netbox-branching API)
- Wait for branch READY status
- Create CR linked to the branch with a policy
- Make changes in branch context (
X-NetBox-Branchheader) - Set CR to
needs-review(or create it asneeds-review) - Reviewers submit reviews with
status: approved - Policy satisfied → CR auto-transitions to
approved - Merge branch → merge gating confirms approved CR → merge proceeds
- CR auto-transitions to
completed
With protect_main=True, step 4 is enforced — no changes without a branch.
Common Filters
GET /api/plugins/changes/change-requests/?status=needs-review
GET /api/plugins/changes/change-requests/?owner=admin
GET /api/plugins/changes/change-requests/?branch=feature-branch
GET /api/plugins/changes/reviews/?change_request_id=1&status=approved
GET /api/plugins/changes/policy-rules/?policy_id=1
Anti-Patterns
- Status names use hyphens:
needs-review,changes-requested(not underscores) - Owner/user/author fields — read-only, set automatically. Don't POST them.
- Zero-rule policy — always fails. Add at least one enabled rule.
- One active CR per branch (v1.0+) — a second active CR fails with a ValidationError; a rejected/completed CR frees the slot.
- Merge gating is mandatory — no toggle, always active when plugin installed
- protect_main is OFF by default — must explicitly enable in config
approvedis not manually settable — only reached via policy satisfaction- Default policy is UI-only (1.1.0+) — the API never auto-fills
policy; look up?is_default=trueand pass it - Self-approval under
require_independent_review(1.1.0+) — the owner'sapprovedreview is recorded but ignored; a rule whose only eligible reviewer is the owner can never pass for them - Never POST review status
pending— rejected by the API - You can only edit/delete your own reviews, comments, and replies (1.0.2+) — including via bulk PATCH/DELETE (403
You can only modify your own objects.); superusers get no exemption - Malformed bulk request bodies (1.1.3+) — a bulk PATCH/DELETE body that isn't a valid list of
{"id": ...}objects is now passed through to NetBox's own bulk handler on every supported NetBox version, so the error is NetBox's (on 4.7: per-object{"detail", "errors": [...]}), not a plugin message. Fix clients that matched on the old plugin error text
Version Notes
Plugin 1.1.3 (2026-09-09) — NetBox 4.5.2–4.7
- NetBox 4.7 support. Malformed bulk requests no longer pre-validated by the plugin (see anti-pattern 12). Requires branching 1.2.x on 4.7, 1.1.x on ≤4.6.
Plugin 1.1.0–1.1.2 (2026-06 → 2026-07) — NetBox 4.5.2–4.6
- 1.1.0:
Policy.is_default(UI pre-select),Policy.require_independent_review, Policies/Policy Rules nav hidden withoutview_policy/view_policyrule, change summary shown on the CR detail page, status/priority help tooltips. - 1.1.1: review form can't submit
pending; NetBox 4.5.x migration compatibility fix. - 1.1.2: migration marked
fake_on_branchso it applies cleanly to branch schemas.
Plugin 1.0.x (2026-05 → 2026-06)
- 1.0.0: multiple CRs per branch (one active), reopen rejected CRs,
bypasspermission, owner notifications,retrigger/action,context.last_branch_changein CR webhook payloads. - 1.0.2: minimum NetBox 4.5.2; edit/delete of reviews, comments, replies restricted to their owner (also in bulk);
change_commentneeded to resolve/unresolve.
NetBox 4.7.3 (2026-10-13)
?background=truebulk writes carryX-NetBox-Branch/?_branch=into the worker, so protect_main no longer rejects branch-scoped background writes as writes to main. Run ≥ 4.7.3 (4.7.0/4.7.1 leaked token plaintexts into job results; 4.7.3 adds branch-safe background writes and the config-context omit optimisation).
NetBox 4.7 (2026-09-02) — what changes for CR automation
- Event-rule conditions gain
changed/unchangedandsnapshots.prechange.*paths: fire only when a CR becomes approved instead of on every save — see references/api-patterns.md. - Webhook context keys
usernameandrequest_idare gone; userequest.user/request.idin body templates. - Per-object bulk errors and
?background=true(branch header dropped on 4.7.0–4.7.2 — don't use it in a branch there; carried on 4.7.3+ — see protect_main above).
References
- references/cr-lifecycle.md — Complete state machine with all transitions
- references/api-patterns.md — Full API examples for all endpoints
- references/policy-system.md — Policy and rule configuration
- references/protect-main.md — protect_main configuration and behavior