---
layout: doc
title: Publish Kubb snapshots with GitLab CI
description: Add a GitLab CI job that runs kubb studio snapshot on each merge request and compares the generated files with the target branch and the previous pipeline.
order: 4
navigation:
  title: GitLab CI
  icon: i-simple-icons-gitlab
---

> Index of every page on this site: [llms.txt](https://kubb.dev/llms.txt)

# Publish Kubb snapshots with GitLab CI

Publish an installable Kubb Studio snapshot from GitLab CI so reviewers can compare generated files against the target branch and previous pipelines. Store `KUBB_TOKEN` as a masked CI/CD variable, commit your npm lockfile, and keep `kubb` and `tsdown` in the development dependencies so `npm ci` installs the CLI.

## Prerequisites

- A repository with a working `kubb.config.ts` on Kubb 5.6.2 or later. Follow [Installation](/docs/5.x/how-to/installation) if you have not set up Kubb.
- A [Kubb Studio](/docs/5.x/integrations/studio) organization and an organization CI API key, stored as `KUBB_TOKEN` in a secret only trusted pipelines can read. Snapshot installation uses a separate registry key. Create both in Studio under Settings › Developers › Tokens, with type CI for `KUBB_TOKEN` and type Registry for installs.
- `tsdown` in the development dependencies, next to `kubb`, so `npm ci` installs it. A snapshot builds its `dist` files with it, and Kubb does not install it by default: `npm i -D tsdown`.


::steps{level="2"}

## Configure the job

Add this job:

```yaml [.gitlab-ci.yml]
snapshot:
  stage: build
  image: node:22
  script:
    - npm ci
    - npx kubb studio snapshot
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
```

With `kubb` and `tsdown` in the development dependencies, `npm ci` is the only setup. The `rules` run the job on merge request pipelines and on the default branch, so a merge request has a snapshot of its target branch to compare with.

### Post the result as a note

Use `--json` to capture the snapshot result, then post it as a merge request note with this script. It updates the same note on each pipeline and uses only Node:

```js [scripts/kubb-note.mjs]
import { readFileSync } from 'node:fs'

const snapshot = JSON.parse(readFileSync('snapshot.json', 'utf8'))
const marker = '<!-- kubb-snapshot -->'
const counts = (c) => `${c.added.length} added · ${c.changed.length} changed · ${c.removed.length} removed`
const lines = [marker, `Kubb snapshot: \`npm i ${snapshot.url}\``]

const { branchChanges: branch, changes } = snapshot
if (branch) {
  if (branch.base) lines.push(`Changes against \`${branch.branch}\`: ${counts(branch)}`)
  else if (branch.baseFound) lines.push(`No snapshot of \`${branch.branch}\` for this package yet`)
  else lines.push(`No snapshot of \`${branch.branch}\` to compare with`)
}
if (changes?.base) lines.push(`Changes since ${changes.base.commit?.slice(0, 7) ?? changes.base.createdAt}: ${counts(changes)}`)

const { CI_API_V4_URL, CI_PROJECT_ID, CI_MERGE_REQUEST_IID, GITLAB_API_TOKEN } = process.env
const notes = `${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes`
const headers = { 'PRIVATE-TOKEN': GITLAB_API_TOKEN, 'Content-Type': 'application/json' }
const existing = (await (await fetch(`${notes}?per_page=100`, { headers })).json()).find((note) => note.body.startsWith(marker))
const response = await fetch(existing ? `${notes}/${existing.id}` : notes, {
  method: existing ? 'PUT' : 'POST',
  headers,
  body: JSON.stringify({ body: lines.join('\n\n') }),
})
if (!response.ok) throw new Error(`GitLab answered ${response.status}`)
```

```yaml [.gitlab-ci.yml]
  script:
    - npm ci
    - npx kubb studio snapshot --json > snapshot.json
    - '[ -z "$CI_MERGE_REQUEST_IID" ] || node scripts/kubb-note.mjs'
```

> [!IMPORTANT]
> Writing a note needs a project access token with the `api` scope, stored as `GITLAB_API_TOKEN`. `CI_JOB_TOKEN` does not carry it.


## Review and install the result

Studio has no diff view for snapshots. The GitHub Action lists the changed generated files in its pull request comment, and `kubb studio snapshot --json` reports them in `changes` and `branchChanges` with any provider. To consume the generated package, follow [Install the snapshot](/docs/5.x/integrations/ci#install-the-snapshot).

> [!NOTE]
> Snapshots expire after seven days. Schedule a default-branch run if it can go a week without a push.


::

## Sitemap

See the full [sitemap](https://kubb.dev/sitemap.md) for all pages.
