---
title: Releases
description: Version control for content, why custom types have it and built-in modules do not.
---

A **release** is a complete copy of your custom content: the types, their
fields, and every entry including drafts. One release per project is the
**default**, and that is what `/v1/content` serves when you do not ask for
another.

## What it is for

You want to change the shape of your content without breaking the site that is
currently reading it. So:

1. **Clone** the default release. You now have a second, complete copy with its
   own `rel_…` id.
2. **Change it.** Add a field, rename one, restructure a type, edit entries.
   The live site sees none of this; it is still reading the default.
3. **Test it** by pointing a preview deploy at the new release:
   `?release=rel_…`, or the same environment variable set differently.
4. **Promote** it when the new frontend is ready. The release becomes the
   default and every request without a `?release=` follows.

Promotion is a flag flip, not a copy. Nothing is moved at the moment of going
live, so nothing can half-move. Rolling back is the same flip in reverse: the
old release still exists, untouched, at the same id.

## Reading a specific release

```http
GET /v1/content/{type}?release=rel_…
```

Omit the parameter and you get the default. Every response says which release
answered it:

```json
"meta": {
  "release": { "id": "rel_RJuPMMbZ…", "name": "Launch", "default": true }
}
```

## Release ids are staging by obscurity

A non-default release has no other address. Anyone who knows its `rel_…` id can
read it, which is exactly what makes a preview deploy easy, and exactly why
the id must not leak.

> **WARNING: Publishable keys cannot list releases**
>
> `/v1/introspection` includes the full `releases` list for a **secret** key
> only. A publishable key ships in page source, so listing release ids to it
> would hand every visitor an index of your unreleased content.
>
> A publishable key still sees `release`, the one serving its request, whose id
> it either already knew or is the default. The key is *absent* rather than
> empty for publishable keys, so a client can tell "not allowed to see" from
> "there are none".

## Built-in modules are release-independent

Blog articles, knowledge base articles, collections, sections and authors hang
off the **project**, not a release. `?release=` is neither needed nor honoured
on `/v1/blog`, `/v1/kb` or `/v1/authors`, and every response from those
endpoints says so:

```json
"meta": { "module": "blog", "release_independent": true }
```

Concretely:

- Publishing an article shows it immediately, whatever release is default.
- Cloning a release does **not** copy the back catalogue.
- Promoting a release does **not** revert an article somebody edited yesterday.

This is deliberate. A release exists to change the **shape** of content, and
the shape of a blog article is ours, and it does not change when you
restructure your own types. Tying the two together would mean a writer's Tuesday afternoon
correction disappearing because a developer promoted a release on Wednesday.

> **INFO: If your blog seems out of date, releases are not the reason**
>
> It is the first thing people suspect and it is never the cause. Check whether
> the article is published, and check you are reading the project you think you
> are with `GET /v1/ping`.

## What a clone copies

Everything in the custom content model: types, fields, entries, **draft and
published values**, and the publishing timestamps. A cloned release is a
complete working copy, not a published-only export, so a page half-written
when you cloned is still half-written in the copy. That is what you want when
the point is to test a migration.

Media is not copied and does not need to be: an asset belongs to the project,
and both releases reference the same one.