Merge Request Pipelines

Learn how to configure GitLab merge request pipelines. From rules and workflow rules to avoiding duplicate pipelines and handling fork security, build MR-specific pipelines that run the right jobs at the right time.

MR Pipelines Rules GitLab CI/CD
What are Merge Request Pipelines?

A merge request pipeline is a pipeline that runs specifically for a merge request, not for every branch push. It runs on the contents of the source branch and is triggered when you create an MR, push a new commit to the MR's source branch, or manually run a pipeline from the MR's Pipelines tab. These pipelines are labeled "merge request" in the pipeline list, making it easy to distinguish them from branch pipelines .

The key difference from a branch pipeline is context. A branch pipeline runs on every push to a branch — even if that branch has no open merge request. A merge request pipeline only runs when there's an active MR, and it has access to additional predefined variables like $CI_MERGE_REQUEST_IID, $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, and $CI_MERGE_REQUEST_TARGET_BRANCH_NAME. These variables let jobs tailor their behavior specifically to the MR context .

Merge request pipelines are not enabled by default. You must configure your .gitlab-ci.yml to run jobs when $CI_PIPELINE_SOURCE == "merge_request_event". This can be done per-job using rules, or for the entire pipeline using workflow:rules. Once configured, MR pipelines become the primary feedback mechanism for code review: every push to the MR triggers the pipeline, and the results appear directly in the MR widget .

Core Principle: Use MR pipelines to validate changes before merge. They run on the source branch, have MR-specific variables, and their status is displayed in the MR. Don't run your full production pipeline on every branch push — that's what MR pipelines are for.

MR Pipeline

Runs when an MR is created, updated, or manually triggered. Runs on source branch only. Labeled "merge request".

rules

Per-job condition. Use if: $CI_PIPELINE_SOURCE == "merge_request_event" to run a job in MR pipelines.

workflow:rules

Pipeline-level condition. Controls whether the entire pipeline runs for MR events, branches, tags, etc.

Duplicate Pipelines

When both branch and MR pipelines run for the same push. Use $CI_OPEN_MERGE_REQUESTS to prevent duplicates.

Merged Results

Runs on the result of merging source into target. Catches integration issues before merge.

Fork Security

Fork MRs run pipelines in the fork project by default, not the parent. Parent members can trigger parent pipelines with warnings.
Configuring with rules

The simplest way to add jobs to merge request pipelines is the rules keyword. A rule with if: $CI_PIPELINE_SOURCE == "merge_request_event" makes the job run only in MR pipelines. You can combine this with changes to run a job only when specific files change — for example, running frontend tests only when frontend files are modified .

Rules are evaluated per-job, which gives you fine-grained control. A build job might run in both branch and MR pipelines, while a deploy job runs only on the default branch. A lint job might run only on MRs. This flexibility lets you design a pipeline that runs the right jobs in the right context, minimizing wasted compute and maximizing relevant feedback.

The only: merge_requests syntax is the legacy equivalent of the rules approach. It still works, but GitLab recommends rules for new configurations because it's more expressive and supports conditions like changes and exists. If you're maintaining an existing pipeline with only, it's worth migrating to rules when you have the chance .

# Run a job only in merge request pipelines job1: script: - echo "This job runs in merge request pipelines" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" # Run a job only when specific files change in an MR lint: script: - echo "This job runs only when JavaScript files change" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - "*.js" - "frontend/**/*" # Run a job in both MR and branch pipelines build: script: - echo "Build runs everywhere" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH # Legacy syntax (only: merge_requests) legacy_job: script: - echo "This job runs in merge request pipelines" only: - merge_requests # Available MR variables in jobs: # $CI_MERGE_REQUEST_IID - MR number # $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME # $CI_MERGE_REQUEST_TARGET_BRANCH_NAME # $CI_MERGE_REQUEST_TITLE # $CI_MERGE_REQUEST_LABELS # Example: use MR variables in script comment_mr: script: - echo "Processing MR !$CI_MERGE_REQUEST_IID" - echo "Source: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" - echo "Target: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"
Rules Best Practices:
  • Use rules instead of only/except for new pipelines
  • Combine if: merge_request_event with changes to run jobs only when relevant files change
  • Use MR variables ($CI_MERGE_REQUEST_IID) in scripts for MR-specific behavior
  • Test rules with different pipeline sources to ensure jobs run in the right context
Configuring with workflow:rules

While rules controls individual jobs, workflow:rules controls the entire pipeline. If the workflow rules evaluate to when: never, no pipeline runs at all — no jobs are evaluated. This is useful for preventing pipelines in contexts you don't care about, like scheduled pipelines or pushes to certain branches .

A common pattern is to run pipelines only for merge requests, the default branch, and tags. This covers the important contexts (review, integration, release) while skipping feature branch pushes that aren't part of an MR. The workflow rules are evaluated before any jobs, so the pipeline is created or not created based on these rules alone.

Another common pattern is switching from branch pipelines to MR pipelines when an MR is opened. Without this, a push to a branch with an open MR triggers both a branch pipeline and an MR pipeline — duplicate pipelines that waste resources and create confusion. The $CI_OPEN_MERGE_REQUESTS variable lets you detect this situation and suppress the branch pipeline, leaving only the MR pipeline .

# Run pipelines only for MRs, default branch, and tags workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG # Switch from branch to MR pipeline when MR is open # Prevents duplicate pipelines workflow: rules: - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_COMMIT_BRANCH - if: $CI_PIPELINE_SOURCE == "merge_request_event" # What this does: # - If a branch pipeline is triggered but an MR is open # for that branch, the branch pipeline is suppressed # - If no MR is open, the branch pipeline runs # - MR pipelines always run # Prevent pipelines for draft MRs workflow: rules: - if: $CI_MERGE_REQUEST_TITLE =~ /^Draft:/ when: never - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Prevent infinite loops (e.g., release job creates tag) workflow: rules: - if: $CI_COMMIT_TAG when: never - when: always # Common workflow:rules examples: # - Run for MRs only # - Run for MRs and default branch # - Run for MRs, default branch, and tags # - Run for MRs, default branch, tags, and schedules # - Exclude drafts, WIP, or specific commit messages
Workflow Rules Gotchas: