Developer Tools8 min read

How to Integrate Bitbucket with Your Headless CMS

Connect Bitbucket to a headless CMS so published content can update repo files, docs, changelogs, config, and static site builds without copy-paste commits.

Published April 30, 2026
01 โ€” Overview

What is Bitbucket?

Bitbucket is Atlassian's Git hosting and code collaboration platform for teams that want repositories, pull requests, branch permissions, Pipelines, and Jira integration in one developer workflow. It's commonly used by engineering teams already working in Atlassian Cloud, especially when code review, CI/CD, and issue tracking need to stay connected. Its core capability is managing Git repositories and automation around code changes, including commits, pull requests, and build pipelines through the Bitbucket Cloud REST API.


02 โ€” The case for integration

Why integrate Bitbucket with a headless CMS?

Developer tools teams often have content that needs to live close to code: product docs, API references, SDK changelogs, starter templates, localization files, release notes, README snippets, and generated JSON config. When that content is edited in one place and manually copied into Bitbucket later, the drift starts fast. A release note gets updated in the editor but not in the repo. A docs page ships with stale API parameter names. A developer opens a PR just to paste content that was already approved somewhere else.


03 โ€” Architecture

Architecture overview

A typical Sanity and Bitbucket integration starts when an editor publishes a document in Sanity Studio. Sanity writes the structured document to the Content Lake, then a webhook fires for matching mutations, such as documents of type docsPage, releaseNote, or apiReference. The webhook can call your own endpoint, or you can use a Sanity Function to run the server-side sync logic without maintaining a separate worker. The sync code fetches the latest document from the Content Lake with GROQ, including only the fields Bitbucket needs, such as title, slug, body text, version, related product, and last updated date. It then formats that content as Markdown, JSON, YAML, or another repo-friendly file type. Finally, it calls the Bitbucket Cloud REST API, usually POST /2.0/repositories/{workspace}/{repo_slug}/src, to create a commit that writes the generated file to a branch. From there, Bitbucket Pipelines can build docs, open a preview, run checks, or deploy a static site that end users read.


04 โ€” Use cases

Common use cases

๐Ÿ“š

Docs-as-code publishing

Publish approved Sanity docs pages as Markdown files in a Bitbucket repo, then let Bitbucket Pipelines build and deploy the docs site.

๐Ÿงพ

Release note commits

Turn Sanity release note documents into versioned CHANGELOG.md updates tied to product releases, branches, or tags.

โš™๏ธ

Generated config files

Sync structured feature copy, navigation, or integration metadata from the Content Lake into JSON or YAML files used by developer portals.

๐Ÿ”

API reference updates

Write API description content from Sanity into Bitbucket so schema docs, examples, and SDK documentation stay aligned with code review.


05 โ€” Implementation

Step-by-step integration

  1. 1

    Set up Bitbucket access

    Create or choose a Bitbucket Cloud workspace and repository. In Bitbucket, create an app password under Personal settings with repository write access, or use OAuth if you need per-user authorization. Record the workspace ID, repo slug, username, and app password. If your repo uses branch restrictions, create a dedicated branch such as sanity-content-sync.

  2. 2

    Model the content in Sanity Studio

    Define schemas for the content you want to commit, such as docsPage, releaseNote, or apiReference. Include fields Bitbucket will need as files: title, slug, body, product, version, status, and outputPath. Schema-as-code keeps these definitions in version control, which matters when content shape affects generated repo files.

  3. 3

    Create a publish trigger

    Add a Sanity webhook filtered to the document types you want to sync, such as _type == 'docsPage' && defined(slug.current). Trigger it on create, update, and publish events. If you don't want to host middleware, use a Sanity Function to run the same logic when content changes.

  4. 4

    Fetch the exact content with GROQ

    In your webhook handler or Function, use @sanity/client to fetch the latest document from the Content Lake. Use GROQ projections to select only the fields Bitbucket needs, and join referenced content such as product names, versions, or owners in the same query.

  5. 5

    Write to Bitbucket with the REST API

    Format the Sanity document as Markdown, JSON, or YAML. Call Bitbucket Cloud's create commit endpoint, POST /2.0/repositories/{workspace}/{repo_slug}/src, with multipart form data that includes branch, message, and a file field named by the target path.

  6. 6

    Test the end-to-end flow

    Publish a test document in Sanity Studio, confirm the webhook response, verify the new commit in Bitbucket, and check that Bitbucket Pipelines runs as expected. Test deletes and slug changes separately, since those may need file removal or redirect files rather than a simple update.


06 โ€” Code

Code example

Minimal Express webhook handler that receives a Sanity webhook, fetches the current document with GROQ, and commits a JSON file to Bitbucket Cloud.

typescript
import express from 'express';
import {createClient} from '@sanity/client';

const app = express();
app.use(express.json());

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-02-01',
  useCdn: false,
  token: process.env.SANITY_READ_TOKEN,
});

app.post('/webhooks/sanity-to-bitbucket', async (req, res) => {
  const id = req.body._id || req.body.ids?.updated?.[0];

  const doc = await sanity.fetch(
    `*[_id == $id][0]{
      title,
      slug,
      "bodyText": pt::text(body),
      "product": product->name,
      _updatedAt
    }`,
    {id}
  );

  const filePath = `content/docs/${doc.slug.current}.json`;
  const form = new FormData();
  form.set('branch', 'main');
  form.set('message', `Sync ${doc.title} from Sanity`);
  form.set(filePath, JSON.stringify(doc, null, 2));

  const auth = Buffer.from(
    `${process.env.BITBUCKET_USERNAME}:${process.env.BITBUCKET_APP_PASSWORD}`
  ).toString('base64');

  const url = `https://api.bitbucket.org/2.0/repositories/${process.env.BITBUCKET_WORKSPACE}/${process.env.BITBUCKET_REPO_SLUG}/src`;
  const response = await fetch(url, {
    method: 'POST',
    headers: {Authorization: `Basic ${auth}`},
    body: form,
  });

  if (!response.ok) {
    throw new Error(`Bitbucket commit failed: ${await response.text()}`);
  }

  res.status(200).json({ok: true, filePath});
});

app.listen(3000);

07 โ€” Why Sanity

How Sanity + Bitbucket works

Build your Bitbucket integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect published content with Bitbucket repositories, Pipelines, and developer workflows.

Start building free โ†’

08 โ€” Comparison

CMS approaches to Bitbucket

CapabilityTraditional CMSSanity
Repo-ready content shapeContent often lives as pages or HTML, so generating clean Markdown, JSON, or YAML usually takes custom parsing.The Content Lake stores structured JSON, and GROQ can return exactly the fields needed for a Bitbucket file.
Commit on publishTeams often export content manually or run scheduled jobs that can lag behind approved changes.Webhooks or Functions can run sync logic when content changes, including server-side commits to Bitbucket without separate infrastructure.
Field-level control for generated filesFile output is often tied to templates, which makes field-by-field repo exports harder to control.GROQ can filter, project, join references, sort, and slice in one query before writing to Bitbucket.
Developer workflow fitContent models are often configured in an admin UI, so schema changes may not follow the same review flow as code.Schema-as-code lets teams review content structure changes in Git, while Sanity Studio gives editors a tailored workspace.
Handling deletes and slug changesDeleting or renaming content may not map cleanly to removing files, adding redirects, or updating navigation in a repo.Webhook filters and Functions can route delete, publish, and update events to different Bitbucket actions, such as removing a file or writing a redirect map.
Multi-channel reuseContent may be shaped around one website, which makes repo sync, app delivery, and agent access separate projects.One structured back end can feed Bitbucket, web, mobile, and production AI agents through Agent Context.

09 โ€” Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Bitbucket and 200+ other tools.