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.
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.
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.
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.
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.
Step-by-step integration
- 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
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
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
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
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
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.
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.
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);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 โCMS approaches to Bitbucket
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Repo-ready content shape | Content 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 publish | Teams 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 files | File 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 fit | Content 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 changes | Deleting 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 reuse | Content 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. |
Keep building
Explore related integrations to complete your content stack.
Sanity + GitHub
Create commits, pull requests, or docs updates in GitHub from structured content published in Sanity.
Sanity + GitLab
Sync release notes, documentation, and generated config from Sanity into GitLab repositories and CI pipelines.
Sanity + Storybook
Use structured content from Sanity to test component states, documentation examples, and editorial previews in Storybook.