Merge Request Approval Rules in GitLab: Configuring Mandatory Reviewers
AI generated
CI/CD
.yml
GitLab · Code Review · Team Workflow
Merge Request Approval Rules
Mandatory reviewers, not an honor code

Approval rules make sure code review does not depend on individual good will but is technically enforced, with CODEOWNERS integration, minimum approval counts, and clear rules for exceptions.

17 min read CODEOWNERS Branch Protection Merge Checks

1. Why a four-eyes principle alone is not enough

Many teams introduce code review as an organizational rule: every merge request needs an approval before it can be merged. That sounds like a solid foundation at first, but in practice it regularly fails at the enforcement stage. Without technical backing, any team member with sufficient rights can simply ignore the rule, whether due to time pressure, lack of awareness, or because a hotfix needs to go out fast. By the end of the quarter it often turns out that a quarter of all merges happened without a real review.

GitLab solves this with approval rules, defined at the project or merge request level, which in combination with branch protection technically prevent a merge request from reaching a protected branch without the required approvals. The key difference from a plain team rule: enforcement no longer rests with the developers but with the platform itself. This significantly reduces debates about exceptions because the rule applies equally to everyone and cannot be interpreted individually.

2. CODEOWNERS as the basis for area-specific review obligations

The CODEOWNERS file in the project root (or under .gitlab/ or docs/) defines which people or groups are responsible for specific paths in the repository. GitLab reads this file and can automatically derive approval rules from it, so that changes to the payment module must be approved by the payments group, while frontend changes are reviewed by a different team. This prevents a reviewer from approving code they have no domain context for.

The order of the rules matters: later, more specific entries override earlier, more general ones. A wildcard entry * at the top of the file covers the entire repository, while targeted path patterns further down define additional, stricter requirements for critical areas such as app/code/Vendor/Payment/. This makes it possible to model a tiered protection level without maintaining every single file by hand.


# .gitlab/CODEOWNERS

# Default rule: at least one review from the core team
* @mironsoft/core-reviewers

# Payment and checkout code: mandatory payments team review
app/code/Vendor/Payment/**       @mironsoft/payments-team
app/code/Vendor/Checkout/**      @mironsoft/payments-team @security-lead

# Frontend/theme: frontend team is responsible
app/design/frontend/**           @mironsoft/frontend-team

# CI/CD configuration: only the platform team may change pipelines
.gitlab-ci.yml                   @mironsoft/platform-team
/ci/**                           @mironsoft/platform-team

3. Setting a minimum number of approvals per rule group

Besides CODEOWNERS mapping, GitLab lets you define a minimum number of approvals for every approval group. A single approval is enough for most changes, but security-critical areas like authentication or payment processing benefit from two independent approvals from different teams. This granularity is the real value over a single global one-approval rule, because it puts effort and risk into a sensible relationship instead of treating a typo fix the same as a change to payment logic.

In the project configuration under Settings > Merge requests > Merge request approvals, you can define several rules in parallel, each with its own target group, its own path filter, and its own minimum count. GitLab evaluates all matching rules for a merge request and requires every single one to be satisfied before the merge button becomes active. This allows for fine-grained escalation logic without needing a separate external tool.


# Example approval rule definition via the GitLab API
# (POST /projects/:id/approval_rules)
{
  "name": "Payments Security Review",
  "approvals_required": 2,
  "user_ids": [],
  "group_ids": [4821, 4903],
  "protected_branch_ids": [1],
  "applies_to_all_protected_branches": false
}

# Second, more general rule for the rest of the code
{
  "name": "Standard Code Review",
  "approvals_required": 1,
  "group_ids": [4711],
  "applies_to_all_protected_branches": true
}

4. Typical bypass paths and why they exist

Even well-configured approval rules have loopholes if other settings do not keep up. The most common case: a project maintainer has enough rights to temporarily change branch protection rules or bypass the merge request entirely with a direct push to the protected branch. Without explicit restrictions, users with the Maintainer role can also edit approval rules on a per-merge-request basis and remove reviewers afterwards, which undermines the entire mechanism if nobody notices the change.

A second, more subtle problem is self-approval: by default the author of a merge request cannot approve their own change, but without the "Prevent approval by author" option enabled, this does not reliably hold in every GitLab configuration, especially in older projects created before this setting existed. Allowing committers to act as reviewers also erodes the four-eyes principle if two developers wave each other's changes through without real review.

5. Deliberately closing the loopholes: the relevant switches

Under Settings > Merge requests, GitLab offers several switches that specifically counter the loopholes described above. Prevent approval by author stops the MR creator's own approval from counting. Prevent approvals by users who add commits closes the gap where someone pushes additional code unnoticed after the initial approval. Prevent editing approval rules in merge requests forbids maintainers from loosening rules on a per-MR basis, so only the central project settings apply.

For truly critical repositories, it is also worth restricting the permission to change branch protection rules to a very small group of people and enabling Code owner approval required for the protected branch. This means even a maintainer can no longer bypass the CODEOWNERS requirement with a click, but would have to explicitly and traceably change the branch protection configuration, which stays visible in the audit log.


# Setting the relevant options via the GitLab API
curl --request PUT \
  --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/123/approval_rules/settings" \
  --data "prevent_approval_by_author=true" \
  --data "prevent_approval_by_commit_author=true" \
  --data "disable_overriding_approvers_per_merge_request=true"

6. Emergency hotfixes: documenting exceptions instead of loosening rules

The classic objection to strict approval rules is: "What if a critical bug needs to be fixed live at night and no reviewer is reachable?" The wrong answer is to permanently loosen the rule. The right answer is a documented exception process: a small, named group of people with the Owner role may temporarily change branch protection rules in an emergency, but must justify it in an incident ticket and revert the change immediately after the issue is resolved.

In practice, a fixed checklist proves useful for such cases: announce the exception in the team chat, link the ticket reference in the merge request, request a retroactive review from a second team member after merging, and revert the temporary permission change within 24 hours. This process preserves the safety of the rule without becoming an obstacle in a real emergency, and it makes every exception visible in the audit log instead of allowing it silently.

7. Tracing approval events: who approved what and when

GitLab logs every approval, every removal of an approval, and every change to approval rules in the project-level audit log (group-wide as well on Premium/Ultimate licenses). This history is more than a formality: it lets you clarify after the fact why a particular merge request only had one approval instead of two despite critical changes, or whether a rule was briefly disabled. For regulated industries or ISO 27001 audits, this log is often a required proof of a working change management process.

The approval status of every merge request can also be evaluated automatically via the API, for example to generate a monthly report of how many merge requests were merged without the required minimum number of approvals (which should be zero with correctly configured rules), or how often the emergency exception was actually used. A rising number is usually a signal that either the reviewer pool is too thin or the rules are too strict in the wrong place.


# Query a merge request's approval status via the API
curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/123/merge_requests/456/approvals" \
  | jq '{approved: .approved, approved_by: [.approved_by[].user.username]}'

8. Gradual rollout instead of a big-bang switch

Introducing approval rules with maximum strictness overnight usually leads to frustration and creative workarounds in established teams. A three-stage approach has proven effective: first, only a single, general rule with a minimum of one approval is enabled for the entire repository, without CODEOWNERS differentiation. After two to three weeks of getting used to it, the split into functional areas via CODEOWNERS follows, so teams become responsible for their own modules specifically.

Only in the third stage are the stricter switches, such as Prevent approval by author and higher minimum counts for security-critical paths, enabled, accompanied by brief team communication explaining why the change makes sense. This staged approach builds acceptance because developers experience the rules as support rather than a bureaucratic hurdle, and it noticeably reduces the number of exception requests during the rollout phase.

9. Common configuration mistakes and how to avoid them

A recurring mistake is defining approval rules without actually marking the target branch as a protected branch. Without that protection, changes can still bypass the rules entirely via a direct push, regardless of how strictly the merge request rules are configured. Just as common is a CODEOWNERS file with overlapping, contradictory path patterns, where in the end nobody is quite sure which group is actually responsible for which folder.

The table below summarizes the most important levers and shows what behavior each one protects against. It works well as a checklist for a configuration review before approval rules go live for a critical repository.

Setting Protects against Recommended for Location in GitLab
CODEOWNERS file Reviews by people outside the domain All projects with multiple teams .gitlab/CODEOWNERS
Approvals required >= 2 A single opinion as the only control Payment, auth, and security code Settings > Merge request approvals
Prevent approval by author Self-approval by the MR creator All protected branches Settings > Merge request approvals
Prevent approvals by commit authors Unreviewed follow-up commits All protected branches Settings > Merge request approvals
Disable overriding rules per MR Maintainers loosening rules case by case Critical and regulated repositories Settings > Merge request approvals

Mironsoft

CI/CD pipelines, zero-downtime deployments and release automation

Deployments that run without downtime and without the nail-biting?

We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.

Pipeline Review

Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.

Zero-Downtime Deployment

Building symlink releases, health checks and rollback strategies for Magento stores.

CI/CD Automation

Connecting tests, security scans and deployments into one reliable pipeline.

10. Summary

Approval Rules: The Essentials at a Glance

CODEOWNERS

Area-specific mandatory reviewers instead of one global rule for everything.

Minimum count

One or two independent approvals per rule group depending on criticality.

Bypass protection

Enable the prevent switches for self-approval and follow-up commits.

Emergency process

A documented, time-limited exception instead of a permanently loosened rule.

11. FAQ: Approval Rules: The Essentials at a Glance

1What is the difference between branch protection and approval rules?
Branch protection prevents direct pushes to a branch and forces merge requests. Approval rules additionally define how many people from which groups must approve a merge request before it can be merged. Both mechanisms only work fully together.
2Can I use CODEOWNERS without GitLab Premium?
The CODEOWNERS file itself works on the GitLab Free tier, but automatically deriving approval rules with an enforced minimum count and required code owner approval for protected branches is a Premium feature.
3How do I stop developers from waving each other's changes through unreviewed?
Combine a sufficient minimum number of approvals with team-level CODEOWNERS mapping, and enable regular spot-check audits of the approval history to detect patterns such as reciprocal approvals without substantive comments.
4What happens if a code owner is on vacation?
Always define code owners as a group rather than a single person so several team members are eligible to approve. Additionally there should be a documented escalation path in case an entire group is briefly unavailable.
5Should every change to .gitlab-ci.yml get its own approval rule?
Yes, that makes sense since pipeline changes can have far-reaching effects on deployments and secrets handling. A dedicated CODEOWNERS line for .gitlab-ci.yml with the platform team as mandatory reviewer is a common safeguard.
6How many approval rules make sense per project?
In practice, three to five rules are usually enough: one general default rule and two to four specialized rules for critical areas such as payments, authentication, and CI/CD configuration. Too many rules make it hard to keep an overview and to debug blocked merge requests.
7Can I connect approval rules to external tools like Jira?
GitLab does not support this directly, but webhooks and the API can be used to report approval events to external systems, for example to automatically update a Jira ticket once the required approval count is reached.
8What is the difference between optional and required approvals?
Optional reviewers are only notified for awareness and do not block the merge. Required approvals from an approval rule count towards the minimum number and must be satisfied before the merge button becomes active.
9How do I handle very large, long-running merge requests?
Large merge requests should be split into smaller, independently reviewable parts wherever possible. Where that is not possible, an additional interim approval after thematic milestones helps so a single reviewer does not have to hold the entire context in their head for weeks.
10Do I have to maintain approval rules manually, or are there templates?
GitLab lets you define approval rule configurations centrally at the group level and inherit them across all projects in a group. This significantly reduces manual maintenance effort and ensures new projects automatically start with sensible default rules.