Your first release

This tutorial takes a user-visible fix from a named fragment to a GitHub Release body. The example repository is owner/project. It uses squash merges and has a release App or another authorized actor that can push a tag.

        flowchart LR
   fragment["Named fragment"] --> link["Squash merge + PR link"]
   link --> changelog["Tagged changelog"]
   changelog --> notes["GitHub notes"]
   classDef source fill:#dbeafe,stroke:#2563eb,color:#0f172a;
   classDef step fill:#ede9fe,stroke:#7c3aed,color:#0f172a;
   classDef result fill:#dcfce7,stroke:#16a34a,color:#0f172a;
   class fragment source;
   class link step;
   class changelog,notes result;
    

The release path

1. Set up the changelog format

Towncrier collects the fragments. Release-creator supplies the template, but the project chooses which categories exist. This minimal pyproject.toml maps bugfix fragments to the Fixed category:

[tool.towncrier]
name = "project"
directory = "changelog.d"
filename = "docs/changelog.md"
template = "release_creator.fragments:template.jinja2"
title_format = false
orphan_prefix = "+"
issue_pattern = "\\d+"
type = [
  { directory = "bugfix", name = "Fixed", showcontent = true },
]

The orphan prefix lets a contributor name a fragment before a pull request exists. The issue pattern reserves bare numeric stems for issue or pull request numbers, so topic.bugfix.md cannot become a misleading issue reference.

Create docs/changelog.md with the insertion marker:

# Changelog

<!-- towncrier release notes start -->

The marker lets Towncrier insert future releases above older ones. See the format reference for the other categories.

2. Write and merge one user-visible change

Add changelog.d/+cache.bugfix.md to the pull request:

Avoid stale cache reads.

The filename needs no pull request number. The sentence describes the change for users, and the linked handle credits its author. Write another fragment if the same pull request also changes another user-visible behavior. Routine maintenance needs no fragment.

Squash-merge the pull request as Fix stale cache reads (#42). On the default branch, link the merged PR before Towncrier deletes the fragment:

uvx --from release-creator release-creator fragments \
  --repository owner/project

The command finds the squash commit, reads the human PR author through the GitHub CLI, and edits the fragment to read:

Avoid stale cache reads ([#42](https://github.com/owner/project/pull/42)) by [@alice](https://github.com/alice).

It fails if GitHub cannot associate the introducing commit with one merged PR, the PR author cannot be read, or the fragment already has a PR link. In GitHub Actions, give the job pull-requests: read and set GH_TOKEN to github.token. For shared authorship, list each human you verified from the PR and include its merged author. Bot PRs receive no automatic credit. See linking named fragments for the required checkout history.

3. Check the version and build the changelog

This example contains only a bug fix, so --next auto would choose 0.0.1. Choose a minor bump to start the project at 0.1.0. tags --require-newer then checks that no equal or newer numeric tag exists:

git tag --list | uvx --from release-creator release-creator tags \
  --next minor --directory changelog.d
git tag --list | uvx --from release-creator release-creator tags 0.1.0 --require-newer

Now let Towncrier consume the linked fragment and assemble the reviewed changelog section. The date is fixed here so the example output is reproducible; normal release automation can use the current date.

uvx --from towncrier --with release-creator towncrier build \
  --version 0.1.0 --date 2026-10-18 --yes

docs/changelog.md now contains the release section, including the PR link and linked author credit:

# Changelog

<!-- towncrier release notes start -->

<a name="release-0-1-0"></a>

## 0.1.0 (2026-10-18)

<a name="release-0-1-0-fixed"></a>

### Fixed

- Avoid stale cache reads ([#42](https://github.com/owner/project/pull/42)) by [@alice](https://github.com/alice).

Format the assembled file with the project’s Markdown hook, then run the full checks before committing and tagging. The tagged file, rather than the commit list, supplies the GitHub description.

4. Render and publish the GitHub notes

After pushing the tag and publishing the package, render the notes from that tagged checkout:

uvx --from release-creator release-creator notes \
  --changelog docs/changelog.md --tag 0.1.0 \
  --repository owner/project \
  --artifact-url https://pypi.org/project/project/0.1.0/ \
  > release-notes.md

notes uses the 0.1.0 section only. It changes Fixed to the GitHub heading Bug fixes and changes the linked author to an @alice mention. With no previous numeric release, the final link points to this tag’s commits:

<a name="release-0-1-0-fixed"></a>

### Bug fixes

- Avoid stale cache reads ([#42](https://github.com/owner/project/pull/42)) by @alice.

[PyPI release](https://pypi.org/project/project/0.1.0/)

**Full Changelog**: https://github.com/owner/project/commits/0.1.0

Review the complete body, then create the GitHub Release if the repository does not do that in its tag workflow:

gh release create 0.1.0 --verify-tag --title v0.1.0 --notes-file release-notes.md

For later releases, pass --previous to compare two tags. The tag guide shows how to select that predecessor; the notes guide covers contributor credits and reStructuredText changelogs.