In a GitHub release, what do the draft, prerelease, and latest flags control?
answer
- Three flags, three different questions
- One of them withholds the tag itself
- Which one keeps release candidates out of installers
- Newest is not always the one people link to
basics
~20 sDraft keeps a GitHub release private to users with push access and does not create its tag until published. Prerelease publishes it but marks it unstable and excludes it from Latest. The latest marker controls which single release the Latest badge and endpoint resolve to.
solid answer
~50 s**Draft** (`draft: true`) is unpublished: only users with push access can see it, it has no public URL, and if the tag does not already exist GitHub does not create it until you publish. That makes drafts the natural staging area for a pipeline that uploads several assets before revealing anything. **Prerelease** (`prerelease: true`) is fully public but labelled *Pre-release*. Its purpose is exclusion: `GET /repos/{owner}/{repo}/releases/latest` skips prereleases, so installer scripts that follow Latest never pick up your `v3.0.0-rc.1`. **Latest** is a single marker across the repository, controlled on the REST API by `make_latest` with values `"true"`, `"false"` or `"legacy"`. It matters because it drives the Latest badge, the `/releases/latest` endpoint, and the redirect people link to. Patching an old branch is the case where you set it explicitly — you do not want `v1.9.4` stealing Latest from `v2.5.0`.
code
bash · 7 lines# Stage a draft, upload into it, then publish
gh release create v2.6.0 --draft --title "v2.6.0" --generate-notes
gh release upload v2.6.0 dist/api-service-2.6.0.jar dist/api-service-2.6.0.jar.sha256
gh release edit v2.6.0 --draft=false
# Backport on an old line: publish without stealing Latest
gh release create v1.9.4 --notes-file NOTES.md --latest=falsego deeper
Know the three states of a GitHub release: draft is unpublished, prerelease is published but marked unstable, and one release at a time is marked Latest.
Explain the mechanics — a draft withholds the tag until publication and is visible only to users with push access, and prereleases are excluded from the /releases/latest endpoint.
Show the pipeline consequences: draft-then-publish makes multi-asset releases atomic, make_latest: "false" protects Latest during backports, and workflows must key on the published event rather than any release activity.
Own what Latest means as a public contract. Every installer script and README link resolves through it, so the policy for which release claims it is a compatibility decision, not a UI preference.
## Three independent controls They are often muddled because all three affect "which release do people see", but they answer different questions: *is it published at all* (draft), *is it stable* (prerelease), and *which one is the current one* (latest). ## Draft A draft release is a saved, unpublished record. Concretely: - Only users with push access to the repository can see it. There is no public URL, so a link cannot leak it. - **The tag is not created until publication** if it did not already exist. A draft naming `v2.6.0` does not put `v2.6.0` in the repository — which means a draft is safe to abandon. - Assets can be uploaded to it. This is the reason drafts matter to automation: a pipeline that builds artifacts on several platforms can create one draft, have each job upload into it, and publish only once everything landed. The alternative — publishing first and uploading after — briefly shows the world a release with missing files, and anyone downloading in that window gets a broken install. ## Prerelease A prerelease is published and public. What changes is classification: - The UI badges it *Pre-release*. - **It is excluded from Latest.** `GET /repos/{owner}/{repo}/releases/latest` returns the most recent release that is neither a draft nor a prerelease. Every installer script that resolves "the current version" through that endpoint therefore ignores your release candidates automatically — the whole point. Note that GitHub does not parse your version string to decide this. Tagging `v3.0.0-rc.1` does not set the flag; a SemVer prerelease suffix is a convention your tooling reads, while GitHub reads the boolean. Set it explicitly (`--prerelease` on the CLI, `prerelease: true` on the API) or your candidate becomes Latest. ## Latest Exactly one release in a repository is Latest. It drives the badge, the `/releases/latest` API response, and the `/releases/latest` web redirect that people put in READMEs and install scripts. By default GitHub picks it for you, and the naive assumption — "the newest one" — is exactly what bites during maintenance work. Publish a `v1.9.4` hotfix on an old release branch after `v2.5.0` shipped, and you do not want the badge and the endpoint pointing at 1.9.4. The REST API exposes **`make_latest`** with three values: - `"true"` — make this release Latest. - `"false"` — publish without becoming Latest. This is the backport setting. - `"legacy"` — defer to GitHub's default selection behaviour. Prereleases and drafts are never Latest regardless. ## The interaction with events Workflows commonly react to releases with `on: release`. The action types are not interchangeable: publishing a draft is a different moment from saving it, and a prerelease being promoted is different again. If a workflow must run when a release becomes visible to users, key it on the `published` type rather than assuming any release activity means the same thing. Getting this wrong produces the classic "my publish job ran while the release was still a draft with no assets" incident. ## Putting it together A sane pipeline for a multi-artifact project: 1. Tag push triggers the build. 2. Create the release as a **draft**. 3. Each build job uploads its asset into that draft. 4. A final job flips `draft` to false — for a release candidate also setting `prerelease: true`, and for a backport setting `make_latest: "false"`. Every visible state is then a state you intended, which is the entire value of having three flags instead of one.
- Why stage a release as a draft before uploading assets?Because a published release with assets still uploading is publicly downloadable and incomplete — anyone who fetches it in that window gets a broken install. Creating a draft, uploading every artifact into it, then flipping `draft` to false makes the release atomic from a consumer's point of view, which matters most when several build jobs each contribute a file.
- Does tagging v3.0.0-rc.1 automatically mark the release as a prerelease?No. GitHub does not parse the version string; `prerelease` is a boolean you set. A SemVer prerelease suffix is a convention your own tooling may read, but if you do not pass `--prerelease` or `prerelease: true`, the release is treated as stable and can become Latest, which is exactly how candidates end up in installer scripts.
- You are publishing a v1.9.4 hotfix long after v2.5.0. What do you set?Publish with `make_latest: "false"` (or `--latest=false` on the CLI) so the Latest badge and the `/releases/latest` endpoint keep resolving to v2.5.0. Otherwise anyone following Latest silently downgrades to the maintenance line, which is a nasty failure because nothing errors — the install simply installs an older major.
- Which release event type should a publish workflow key on?`published`, which fires when a release becomes visible rather than when a draft is merely saved or edited. Keying on broader release activity risks running the job against a draft that has no assets yet, producing an empty or partial artifact push that is hard to trace after the fact.
saying these in an interview costs you the question
- Thinks a draft release already created its tag
- Believes a -rc suffix sets the prerelease flag
- Assumes the newest release is always Latest
- Says prereleases are hidden from the public
- Publishes first and uploads assets afterwards