Versioned release entries, each carrying a type so you can serve different audiences from one list.
Release types
Every entry is one of seven types:
feature · fix · improvement · breaking · security · deprecated · other
Typing entries is what makes a changelog filterable, and filtering is what makes it useful to more than one reader. An engineer integrating your API cares about breaking and deprecated. A user cares about feature. A security team wants security on its own page.
Listing entries
const response = await kookee.changelog.list({
page: 1,
limit: 10,
search: 'export',
filter: { changelogType: 'feature' },
});
All parameters are optional. limit defaults to 10 and maxes at 100.
Reading the type and version
The release type and version are structured fields, not properties of the entry. entry.type is always the string 'changelog'; the release type lives in entry.fields, and the SDK ships helpers to read it:
import { fieldOptionKey, fieldStringValue } from '@kookee/sdk';
const releaseType = fieldOptionKey(entry.fields, 'changelogType'); // 'feature'
const version = fieldStringValue(entry.fields, 'version'); // '1.6.0'
const link = fieldStringValue(entry.fields, 'link'); // undefined if unset
Both return undefined when the field is unset, which is the common case for older entries.
Filtering
filter takes field slugs and matches on the option key:
const breaking = await kookee.changelog.list({ filter: { changelogType: 'breaking' } });
const patch = await kookee.changelog.list({ filter: { version: '1.4.1' } });
Filtering server-side is worth it. For a dedicated breaking-changes page, filter: { changelogType: 'breaking' } transfers far less data than fetching everything and discarding most of it.
A single entry
const entry = await kookee.changelog.getBySlug('v1-0-kookee-is-live');
entry.contentHtml; // full body — list items do not include this
getById(id) is there when you hold an ID rather than a slug. A missing slug throws a KookeeApiError with status 404.
Reactions
await kookee.changelog.react(entry.id, { reactionType: 'fire', action: 'add' });
await kookee.changelog.react(entry.id, { reactionType: 'fire', action: 'remove' });
Reactions work with your normal API key, so this runs directly in the browser. Store which reactions the current user has added in local storage so your UI can render the toggled state and send the right action. In React, Reactions from @kookee/react/entry/client does this for you.
Treat the resulting counts as product feedback. Shipping ten things a month and seeing which two collected all the reactions tells you where the next month should go, at a cost of one click per user.
Grouping by type
A common layout groups a page by release type rather than listing entries flat. Group on the field value, not on entry.type:
import { fieldOptionKey } from '@kookee/sdk';
const { data } = await kookee.changelog.list({ limit: 100 });
const grouped = data.reduce<Record<string, typeof data>>((acc, entry) => {
const key = fieldOptionKey(entry.fields, 'changelogType') ?? 'other';
(acc[key] ??= []).push(entry);
return acc;
}, {});
Translations
const entries = await kookee.changelog.list({ locale: 'de', fallback: true });
const translations = await kookee.changelog.getTranslationsBySlug('v1-0-kookee-is-live');
fallback defaults to true, returning your default-locale entry when no translation exists.
Ready-made React components
@kookee/react/changelog renders the list with type badges and versions, a filter by release type, and the entry page, all on the server. WhatsNew from @kookee/react/changelog/client adds a badge that counts the entries a visitor has not seen yet, with a popover listing them. See Build a changelog with the React components.
Writing entries that get read
The practical failure is not writing bad entries — it is writing them a week late, in a batch, from memory. By then nobody recalls why a change mattered, and you end up with a list of commit messages.
Write the entry while the pull request is still open. In the dashboard, or from an AI assistant connected over MCP, which can draft the entry, set its type and version, and hand you a link to review — see the article on managing your content from an AI assistant. Publishing takes no deploy and no rebuild, so the entry can sit as a draft until the release actually ships.
Then it is one fetch away, wherever you need it — marketing site, in-app "What's new" panel, release email:
const { data } = await kookee.changelog.list({ limit: 5 });