How to Add a Changelog Page to an AI-Built App
A worked example: a changelog_entries table, a public /changelog route, and how to draft entries with AI from merged commits without auto-publishing them.
A changelog page needs three things to work: a small database table that models one entry per version, release date, and change type, a public route that renders published entries in order, and a review step between whatever drafts an entry and whatever makes it visible to users. The table and the route are the easy half. The review step is the part most AI-built apps skip, and skipping it is what turns a changelog from a nice-to-have into a liability.
This walkthrough builds all three: a changelog_entries table, a public /changelog route, and a prompt you can hand an AI coding agent to draft an entry from a batch of merged commits, along with the point where a person has to step in before anything goes live.
Why a changelog page earns a spot in an AI-built app
Most AI-built apps ship features faster than anyone can explain them. A changelog is the cheapest way to close that gap: it tells existing users what changed without you writing a support reply about it, and it gives you a public record of shipped work that a support conversation, a sales call, or a curious user can point back to. It also barely touches your core data model. It is one new table and one new route, which makes it a reasonable weekend addition even to an app that already has payments, auth, and file uploads bolted on.
Step 1: Design the changelog_entries table
Keep the schema narrow. A changelog entry needs a version, a release date, a category, a title, a body, and a flag for whether it is actually visible yet:
create table changelog_entries (
id uuid primary key default gen_random_uuid(),
version text not null,
release_date date not null,
category text not null check (category in ('added', 'changed', 'fixed', 'removed')),
title text not null,
body text not null,
is_published boolean not null default false,
created_at timestamptz not null default now()
);
create index changelog_entries_release_date_idx
on changelog_entries (release_date desc)
where is_published;A few choices in that schema are doing more work than they look like:
category as a checked enum, not free text. It keeps whatever drafts entries, human or AI, from inventing new labels that then need their own badge color and copy on the frontend.
version as text, not a number. That leaves room for whatever scheme you already use: semantic versioning, a date-based tag, or just "August 2026" if version numbers do not mean much to your users.
body as markdown, rendered at request time. It keeps the table simple and avoids storing pre-rendered HTML that quietly drifts from the source text after an edit.
is_published defaulting to false. The default matters as much as the column. Anything inserted without an explicit true stays hidden until someone reviews it.
Step 2: Build the public /changelog route
The route itself is a straightforward read. Fetch published entries newest first:
select version, release_date, category, title, body
from changelog_entries
where is_published = true
order by release_date desc, version desc;Group the results by version in application code rather than in SQL, since a single release can carry entries from more than one category. A reasonable page structure per version: the version number and date as a heading, then each entry listed under a small category badge, added in green, changed in blue, fixed in amber, removed in grey. Keep the badge colors consistent across every release so a returning user learns the pattern once instead of re-reading it every time.
Step 3: Draft entries from commits, then make a person read them before they ship
This is the part worth building well. Once you have a stream of merged pull requests or commit messages, an AI coding agent can turn a week of work into a drafted changelog entry in the time it takes to run one prompt. That is a genuine time saving over writing release notes by hand, and it is also exactly where things go wrong if the output is allowed to publish itself.
Feed the agent the raw material and ask it to draft, not decide:
Here are the merged PR titles and descriptions since our last release
(list below). Draft one changelog entry per user-facing change.
For each entry:
- Pick a category: added, changed, fixed, or removed.
- Write a one-sentence title in plain language. No ticket numbers,
no internal file or function names.
- Skip anything purely internal (refactors, dependency bumps, test
changes) unless it fixes a bug a user would have noticed.
- Do not invent details that are not present in the PR text.
Output each entry as: category, title, body. Leave version blank,
I will fill that in.Insert whatever it returns with is_published set to false, always. Three things go wrong often enough that skipping the review is not a safe shortcut. The agent occasionally turns an internal refactor into a user-facing bullet because the pull request title happened to read like one. It sometimes phrases an entry from the developer's point of view instead of the user's, "refactored the auth middleware" instead of "fixed a bug where some users were logged out unexpectedly." And it has no way to know which of five bug fixes in a batch was actually visible to customers versus caught in staging before anyone saw it. Someone who shipped the release, or at least skimmed the pull requests, is the one who can tell the difference, and that review is usually a two-minute read and a couple of word edits, not a rewrite.
Good commit messages written with AI make this whole step more reliable, since the drafting agent only has whatever text it is given to work from. Vague commit messages produce vague, harder-to-verify changelog drafts.
Step 4: Review and publish
The simplest review workflow is a boolean flip, not a separate app. If your app already has an admin dashboard, add the changelog table to it as one more list view: draft entries at the top, a preview of the rendered markdown, an edit box, and a publish toggle. Reviewers should see the same page a customer would see, not a raw text field, since markdown mistakes and awkward phrasing hide easily in a text box and jump out immediately once the page is styled.
Set a rule and keep it consistent: nothing gets set to is_published = true without a person other than the drafting agent reading it first, even on days when the release is small and the draft looks obviously fine. The usual failure is not a dramatic hallucination. It is a mundane detail, a feature name that changed since the draft was written, a fix that shipped to staging but not yet production, slipping through because the review step got skipped just this once.
A few details worth getting right
Pick one versioning scheme and keep it. Semantic versioning if you ship a public API, a plain date if you ship continuously and version numbers do not mean much to users.
Sanitize the markdown body before rendering it. A changelog body drafted by an AI agent is still untrusted input the moment it is stored, the same as any other text field a user or agent can fill in.
Order by release_date, not by insertion order. A backfilled or corrected entry should not jump to the top of the page just because it was written last.
Keep it separate from documentation, but link between them. A changelog records what changed, not how to use it. If you are already using AI to write documentation for the app, cross-link a changelog entry to the relevant doc page instead of duplicating the explanation.
Wire up basic tracking if you want to know whether anyone reads it. Adding analytics to an AI-built app and watching pageviews on /changelog answers that faster than guessing.
Keep old entries. Deleting a published entry to hide a mistake is more visible than leaving it and adding a correction note. Treat the changelog as a public record once something has been live for a while.
Frequently asked questions
Do I need a changelog page for a small app?
Not urgently, but it costs little to add and pays off the first time a user asks "did you fix that bug" or a support conversation could have been a link instead of an explanation. It is more useful the moment you have any users who return to the app regularly.
Should a changelog be public or behind login?
Public, in most cases. A public changelog builds trust with prospective users and gives search engines and support content something to link to. Keep it private only if the entries themselves reveal something competitively sensitive, in which case consider a separate internal log instead of hiding the whole page.
Can I auto-generate a changelog with AI?
Yes, drafting is a good use of an AI coding agent. Feed it merged pull request titles and descriptions and it can produce a reasonable first pass at each entry. The part that should not be automated is publishing. Insert AI-drafted entries with is_published set to false and have a person confirm the category, wording, and whether the change was actually user-facing before it goes live.
What is the difference between a changelog and release notes?
In practice the terms overlap. "Release notes" more often means one longer writeup tied to a specific version bump, while a "changelog" more often means an ongoing, scannable list of smaller entries grouped by category. The data model in this guide supports both: group by version for release notes, or show the flat list for a running changelog. For the longer writeup format, see how to prompt AI to write release notes that don't read like a diff.
How often should I publish changelog entries?
Match your release cadence rather than a fixed schedule. Teams shipping continuously often batch entries weekly so the page does not turn into noise, while teams on a slower release cycle publish one entry per release. Consistency matters more than frequency, since users check a changelog for the pattern, not the exact interval.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


