Draft Pull Requests
A draft pull request is a normal pull request with one extra flag flipped on: it tells GitHub and everyone watching the repository that the code inside is not ready for review yet. You get all the benefits of a real PR — a diff against main, a place for comments, and automatic CI runs — without anyone assuming you’re asking them to approve or merge it. This makes drafts the standard way to share work-in-progress code, get early feedback on an approach, or let continuous integration catch problems before you formally request review.
Overview: What a Draft Pull Request Actually Is
To understand draft PRs you need to remember what a pull request is under the hood: it is not a special Git object. There’s no new blob, tree, or commit created when you open one. A pull request is a GitHub database record that says “compare the tip of this head branch against this base branch,” plus metadata — a title, a description, comments, requested reviewers, and a handful of boolean flags. One of those flags is draft. When it’s true, GitHub renders a grey “Draft” badge instead of the green “Open” badge, and it disables the merge button in the UI. When it’s false, the PR is “ready for review” and behaves like any other open PR.
Because a draft PR is the exact same underlying object as a regular PR, everything else about it works identically:
- Every
git pushto the head branch still appends new commits to the PR’s timeline — draft status doesn’t pause that. - GitHub Actions workflows triggered on the
pull_requestevent still fire for draft PRs by default. Drafts do not pause CI. - You can still leave comments, push fixup commits, and even request reviews on a draft, though most teams wait until it’s marked ready before pinging reviewers.
- On a public repository, a draft PR is just as publicly visible as any open PR — “draft” is a workflow signal, not an access-control setting.
Draft PRs replace the older convention of prefixing a PR title with WIP: or [WIP]. A text prefix is just a string a human has to notice; the draft flag is a structured, machine-readable field that GitHub’s UI, search (is:draft), branch protection rules, and the API can all act on.
Syntax
You can create or convert a draft PR either from the GitHub web UI or with the gh command-line tool. The web UI adds a dropdown arrow next to the “Create pull request” button that lets you choose “Create draft pull request” instead of the default. On an existing PR’s page, a “Convert to draft” link sits near the reviewers panel; once converted, it’s replaced by a “Ready for review” button.
From the command line, the relevant gh forms are:
gh pr create --draft [--title "<title>"] [--body "<body>"] [--base <branch>] [--head <branch>]
gh pr ready [<number> | <branch>]
gh pr ready [<number> | <branch>] --undo
--draft— ongh pr create, opens the new PR with the draft flag set instead of ready-for-review.--title/--body— supply the PR title and description non-interactively; omit them andghopens an editor or prompts you.--base— the branch you want to merge into (defaults to the repository’s default branch, typicallymain).--head— the branch with your changes (defaults to your current branch).gh pr ready <number>— flips an existing draft PR to “ready for review.”gh pr ready <number> --undo— flips a ready PR back to draft.
Examples
Example 1: Opening a draft PR right after pushing a new branch
You’ve just scaffolded a feature and want a place to track it and let CI run, but it’s far from reviewable.
git switch -c feature/password-reset
git add .
git commit -m "feat: scaffold password reset flow"
git push -u origin feature/password-reset
gh pr create --draft --title "feat: password reset flow" --body "Early WIP: routing and form scaffolding only, no email delivery yet. Feedback on the approach is welcome."
Output:
Creating draft pull request for feature/password-reset into main in vega-labs/webapp
https://github.com/vega-labs/webapp/pull/482
Git pushes the branch exactly like it would for any PR — nothing about the push itself is draft-specific. The draft behavior comes entirely from the --draft flag passed to gh pr create, which sets the flag when the PR record is created. The badge on GitHub now reads “Draft,” and the merge button is disabled until someone marks it ready.
Example 2: Marking a draft ready once the work is complete
A few days later the password reset flow is finished, tested, and you want reviewers to look at it.
gh pr ready 482
Output:
✓ Pull request vega-labs/webapp#482 is marked as "ready for review"
This sends a single update to the PR’s draft field — from true to false. No commits, branches, or refs change. GitHub fires a ready_for_review event, which is what any requested reviewers and required-check automation are watching for; the merge button now becomes active once required status checks pass.
Example 3: Sending a ready PR back to draft after review feedback
A reviewer leaves change requests on PR #482. Rather than leaving it looking “ready to merge” while you rework it, you convert it back to draft, push the fix, then re-ready it.
gh pr ready 482 --undo
git add src/auth/reset-password.js
git commit -m "fix: validate reset token expiry before rendering form"
git push
gh pr ready 482
Output:
✓ Pull request vega-labs/webapp#482 is marked as a draft
✓ Pull request vega-labs/webapp#482 is marked as "ready for review"
Converting back to draft doesn’t remove the reviewer’s existing comments or reset approvals in any destructive way — it just re-hides the merge button and signals “don’t look at this yet” while you address feedback. Once you’re done, flipping it ready again re-opens it for another look.
How Draft Status Works Internally
git pushuploads your local commits torefs/heads/feature/password-reseton the remote. This step is identical whether or not a PR, draft or otherwise, exists.gh pr create --draftcalls GitHub’s API to create a pull request resource that references the head ref and the base ref (main), and sets itsdraftboolean totrue. The diff GitHub shows is computed the same way as for any PR: the merge base between the two branches, plus every commit reachable from the head but not the base.- Because the flag lives on the PR resource, not on the branch or any commit, later pushes to the same branch keep updating the same PR — draft status is unaffected by new commits.
- Workflow files that trigger
on: pull_requeststill run for drafts unless you explicitly add a condition (see the Common Mistakes section below) — GitHub does not treat draft as “paused.” gh pr readyissues an update that flipsdrafttofalseand triggers apull_requestwebhook event withaction: ready_for_review. Branch protection rules and required reviewers only start actively blocking merges once the PR is in this ready state.gh pr ready --undoflips the same field back totrue. Every comment, commit, and review left up to that point stays exactly as it was — only the flag and the merge-button availability change.
Common Mistakes
Mistake 1: Trying to merge a draft PR
gh pr merge 482 --squash
GitHub refuses this with an error to the effect of “pull request is in draft state and cannot be merged.” This is by design — the draft flag exists specifically to prevent an unfinished PR from being merged by accident, even if someone bypasses the greyed-out button in the UI and goes straight to the CLI or API.
Fix: mark it ready first, then merge.
gh pr ready 482
gh pr merge 482 --squash
Mistake 2: Letting CI burn minutes (or run untrusted forks) on every draft push
Teams sometimes assume draft PRs are “quiet” and are surprised when dozens of half-finished WIP pushes each trigger a full test suite, wasting Actions minutes or, for public repositories accepting fork PRs, running workflows against untrusted code before a human has even looked at it.
Fix: gate expensive jobs on the draft flag inside the workflow itself, and only run the full suite once a human converts the PR to ready:
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
test:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
This still lets lightweight checks (linting, formatting) run on every draft push if you want fast feedback, while reserving the heavier or more sensitive jobs for when the PR is actually ready for review.
Mistake 3: Assuming “draft” means “private”
On a public repository, a draft pull request’s diff, commits, and comments are visible to anyone who can view the repository — which, on a public repo, means the entire internet. Draft only hides the PR from the “please review this” workflow; it is not an access-control mechanism. Committing a secret, API key, or sensitive customer data to a draft branch on a public repo exposes it just as much as doing so on a ready PR.
Fix: treat every branch you push to a shared or public remote as visible, draft or not. Keep secrets out of commits entirely and use environment variables or a secrets manager instead.
Best Practices
- Default to opening a draft PR as soon as you push a branch you’ll be iterating on for more than an hour or two — it gives you a running diff, CI feedback, and a place for teammates to comment without implying “please review this now.”
- Write a short checklist of remaining TODOs in the PR description so anyone who peeks at the draft knows what’s finished and what isn’t.
- Use
gh pr ready <number>deliberately as the single moment you signal “this is done” — avoid staleWIP:title prefixes now that the structured draft flag exists. - Periodically audit stale drafts with
gh pr list --draftor theis:pr is:draftsearch qualifier on GitHub, and close or finish anything that’s been sitting for weeks. - Gate expensive or secret-using CI jobs behind
github.event.pull_request.draft == falseso drafts don’t waste Actions minutes or expose secrets to unreviewed code. - Hold off assigning reviewers until you mark the PR ready — requesting review on a draft creates notification noise before there’s anything to actually review.
- If a reviewer requests changes, convert back to draft with
gh pr ready --undowhile you rework it, then re-ready it, rather than leaving a “changes requested” PR sitting in a confusing half-state.
gh pr list --draft
Practice Exercises
- Create a new branch off
main, make a small change to a file, commit it, and push the branch. Open a draft pull request for it with a description listing at least two things still left to do. Then run a command to list only the draft PRs in the repository and confirm yours shows up. - Pretend the work from exercise 1 is finished: mark the PR ready for review. Check what changes in the GitHub web UI (badge color, merge button). Then attempt to merge it and observe what happens if required checks haven’t passed yet.
- Simulate a reviewer requesting changes on a ready PR: convert it back to draft, make one more commit addressing the (imagined) feedback, push it, and mark it ready again. Confirm the PR’s comment history from before you converted it back to draft is still intact.
Summary
- A draft pull request is a regular PR with a
draftboolean flag set totrue— no new Git objects, branches, or refs are involved. - Create one with
gh pr create --draftor the dropdown next to “Create pull request” on GitHub; the draft flag disables the merge button and shows a grey “Draft” badge. - Convert to ready with
gh pr ready <number>, and back to draft withgh pr ready <number> --undo; both only flip the flag and leave commits, comments, and reviews untouched. - GitHub Actions workflows still run on draft PRs by default — gate heavy or sensitive jobs on
github.event.pull_request.draft == falseif you want to skip them until the PR is ready. - Draft status is a workflow signal, not an access control — on public repositories, drafts are just as publicly visible as any other open PR.
- GitHub blocks merging a draft PR outright, even via the CLI or API, until it’s explicitly marked ready.
