DEV Community

Cover image for GitHub Stacked PRs: A Practical Guide to Smaller, Faster Code Reviews
Arindam Majumder
Arindam Majumder Subscriber

Posted on • Originally published at arindamm.dev

GitHub Stacked PRs: A Practical Guide to Smaller, Faster Code Reviews

Large pull requests are difficult to review well.

They combine too many concerns, make feedback harder to act on, and often leave reviewers deciding where to start.

GitHub Stacked PRs offers a more deliberate workflow: split one large change into a sequence of small, dependent pull requests (PRs) that can be reviewed independently.

If you need a visual demonstration, I have also made a video walkthrough of this workflow.

In this tutorial, you will learn what stacked PRs are, when to use them, and how to manage a complete stack using GitHub CLI. We will use a small authentication feature as an example, breaking it into database, API, and interface layers.

What Are GitHub Stacked PRs?

Sacked PRs

A stacked pull request is a chain of two or more PRs in the same repository. The bottom PR targets your trunk branch, usually main, and every PR above it targets the branch directly below it.

For example, an authentication feature might be organized like this:

feat/login-ui        → PR #3 (base: feat/auth-api)
feat/auth-api        → PR #2 (base: feat/user-model)
feat/user-model      → PR #1 (base: main)
main
Enter fullscreen mode Exit fullscreen mode

Each layer contains one discrete, reviewable change. A reviewer opening feat/auth-api sees the API work introduced by that layer, not the database work already included in feat/user-model.

GitHub also displays the stack relationship in the pull request interface, so the full context remains available.

Stacked PRs

This differs from simply splitting a feature into arbitrary branches. The order matters: a layer can depend on code in the same branch or a lower branch, but not on code higher in the stack. Put foundational work, such as schema changes and shared types, near the bottom; put code that consumes those foundations, such as endpoints and UI components, above it.

Why Use Stacked PRs?

Stacked PRs are useful when a change is too large for one focused review but its pieces must be developed in order. They let you open the first layer for review and immediately continue building the next layer instead of waiting for a merge.

The main benefits are:

  • Smaller, more focused diffs: Reviewers can concentrate on one concern at a time.
  • Earlier feedback: You can request feedback on a data model or API contract before the rest of the feature is complete.
  • Clearer dependencies: The branch structure documents which work builds on which foundation.
  • Safer high-volume development: This is especially helpful when coding agents generate a larger feature in several logical steps.
  • Less manual Git work: GitHub can cascade rebases through a stack when lower layers change or merge.

Stacks are not the right answer for every change. Use a normal PR for a small, self-contained fix. A stack adds coordination overhead, so its layers should be meaningful on their own, not tiny fragments created only to increase the number of PRs.

Prerequisites

Before starting, make sure you have:

  • A GitHub repository with a default branch such as main.
  • GitHub CLI version 2.0 or later installed and authenticated. If needed, run gh auth login.
  • The GitHub Stacked PRs extension installed:
gh extension install github/gh-stack
Enter fullscreen mode Exit fullscreen mode

GitHub Stacked PRs is currently in public preview, so the interface and commands may evolve. All branches in a stack must also live in the same repository; cross-fork stacks are not supported.

Create Your First Stack

We will create a three-layer stack for an authentication feature:

  1. A user model.
  2. Authentication API routes.
  3. A login interface.

Start from an up-to-date local copy of your default branch:

git switch main
git pull --ff-only origin main
Enter fullscreen mode Exit fullscreen mode

1. Initialize the bottom layer

Run gh stack init with a branch name to create the first branch and register a stack locally:

gh stack init feat/user-model
Enter fullscreen mode Exit fullscreen mode

The bottom branch is based on the repository's default branch unless you explicitly choose another base with --base. Make the database or domain-model changes on this branch, then commit them as you normally would:

git add src/models/user.ts migrations/
git commit -m "Add user model"
Enter fullscreen mode Exit fullscreen mode

You can also run gh stack init without a branch name and select the first layer interactively.

2. Add a dependent API layer

Once the user-model work is committed, create a branch above it:

gh stack add feat/auth-api
Enter fullscreen mode Exit fullscreen mode

This creates and checks out feat/auth-api at the current HEAD. Implement the login and session routes, then commit the work:

git add src/routes/auth.ts src/services/session.ts
git commit -m "Add authentication API"
Enter fullscreen mode Exit fullscreen mode

The API branch now contains the commits from the user-model layer plus its own commits. However, its eventual PR diff will show only the API-specific changes because its base branch is feat/user-model.

3. Add the UI layer

Create the third layer while you are on the current top branch:

gh stack add feat/login-ui
Enter fullscreen mode Exit fullscreen mode

Add the form and client-side integration, then commit:

git add src/components/LoginForm.tsx src/pages/login.tsx
git commit -m "Add login interface"
Enter fullscreen mode Exit fullscreen mode

Use gh stack view at any point to inspect the local order, branch names, associated PR links, and recent commits:

gh stack view
Enter fullscreen mode Exit fullscreen mode

Tip: Add a new branch when the concern changes or the current layer has grown large enough to make review difficult. A database migration, an API contract, and a UI implementation are usually better review units than three random groups of files.

Submit the Stack to GitHub

When the layers are ready, submit them together:

gh stack submit
Enter fullscreen mode Exit fullscreen mode

This command pushes every branch, creates or updates a PR for each branch, and links the PRs as one stack on GitHub. In an interactive terminal, it opens an editor where you can review each PR title and description and choose whether it is ready for review or a draft.

For automation or a quick initial submission, use:

gh stack submit --auto
Enter fullscreen mode Exit fullscreen mode

By default, the non-interactive form creates new PRs as drafts. Add --open if the new PRs should be ready for review immediately:

gh stack submit --auto --open
Enter fullscreen mode Exit fullscreen mode

After submission, the stack looks like this on GitHub:

PR #1: Add user model          → main
PR #2: Add authentication API  → feat/user-model
PR #3: Add login interface     → feat/auth-api
Enter fullscreen mode Exit fullscreen mode

The PR page includes a stack map that shows the layers and their status. Reviewers can jump between the PRs without returning to the pull request list.

Review Stacked Pull Requests

The best way to review a complete stack is from bottom to top. Start with the foundational model, then review the API that depends on it, then the interface that uses that API. This mirrors the dependency order and provides the full story.

Each PR should also make sense as a focused review on its own. If reviewing one layer requires holding too much unrelated information in mind, consider restructuring the stack so that the layer becomes more atomic.

GitHub evaluates each stacked PR against the requirements of the bottom PR's base branch. In practice, this means branch protection rules and pull-request CI checks associated with main apply throughout the stack, including layers that directly target another feature branch.

Update a Stack After Feedback

Feedback often arrives on the lower layers first. For example, a reviewer might ask you to rename a field in feat/user-model after you have already built the API and UI on top of it.

Check out the layer you need to change, make the correction, and commit it:

gh stack checkout feat/user-model
# Edit the model files
git add src/models/user.ts
git commit -m "Rename user identifier field"
Enter fullscreen mode Exit fullscreen mode

Then rebase the dependent branches in order:

gh stack rebase
Enter fullscreen mode Exit fullscreen mode

gh stack rebase fetches from the remote and runs a cascading rebase from the trunk upward. If Git reports a conflict, resolve it, stage the resolved files, and continue:

git add <resolved-files>
gh stack rebase --continue
Enter fullscreen mode Exit fullscreen mode

Finally, push the updated branches and refresh their PRs:

gh stack submit
Enter fullscreen mode Exit fullscreen mode

For routine maintenance, gh stack sync is often the simplest option:

gh stack sync
Enter fullscreen mode Exit fullscreen mode

It fetches updates, reconciles the local and remote stack, fast-forwards the trunk when possible, cascades rebases when needed, pushes branches, and synchronizes PR state. If a rebase rewrites branch history, the extension uses --force-with-lease when pushing, which protects against overwriting a remote update you do not have locally.

Merge a Stack

merge stack

Stacked PRs merge from the bottom upward. You can merge one layer, a contiguous portion of the stack, or the whole stack:

  • Merge the bottom PR to land only that PR.
  • Merge a middle PR to land it and every unmerged PR below it.
  • Merge the top PR to land the entire stack.

You cannot merge a middle layer while leaving an unmerged dependency below it. After a partial merge, GitHub automatically rebases and retargets the remaining upper layers so that the next unmerged PR is ready to continue through review.

GitHub supports merge commits, squash merges, and rebase merges for stacks, and the final history matches the result of merging the PRs individually from bottom to top.

Best Practices for GitHub Stacked PRs

Use these guidelines to keep stacks readable and easy to maintain:

  1. Make each layer independently reviewable. A layer can depend on lower layers, but its purpose should be clear in its own title, description, and diff.
  2. Keep the stack shallow when possible. Two to five thoughtful layers are easier to reason about than a long chain of micro-PRs.
  3. Order layers by dependency. Put shared types, migrations, and core logic below their consumers.
  4. Use descriptive branch and PR names. Names such as feat/user-model and feat/auth-api make the stack map immediately understandable.
  5. Submit early as drafts. This gives teammates visibility and lets you ask for architecture feedback before implementation is complete.
  6. Sync before continuing work after lower layers merge. This keeps local branches aligned with GitHub's updated stack.
  7. Explain the stack in the PR descriptions. A short sentence such as “Part 2 of 3: adds the API on top of the user model” helps reviewers understand the intended sequence.

Conclusion

GitHub Stacked PRs turn a large, difficult-to-review change into a series of focused, connected reviews. The workflow is straightforward: initialize a bottom branch, add layers as the work gains dependencies, submit the stack, and keep it synchronized as reviews and merges happen.

Start with a feature that naturally separates into two or three layers. Once your team is comfortable reviewing one focused change at a time, stacked PRs can make large changes feel much more manageable.

Top comments (0)