How to Integrate Jira with Your Headless CMS
Connect Jira to your headless CMS so published docs, release notes, and product content can create or update engineering issues in real time.
What is Jira?
Jira is Atlassian's work tracking platform for software, IT, product, and operations teams. Teams use it to plan epics, track issues, run sprints, assign work, set workflows, and report on delivery. In developer tooling, Jira is often the system of record for bugs, feature requests, release tasks, and engineering review.
Why integrate Jira with a headless CMS?
Content and engineering work often drift apart. A docs editor publishes an API guide, a product marketer updates release notes, or a support team flags a broken setup step, then someone has to copy details into Jira by hand. That usually means missing links, stale acceptance criteria, duplicate tickets, and Jira issues that don't point back to the exact content that triggered the work.,Connecting Jira to a headless CMS helps teams turn content events into trackable engineering work. For example, when a Sanity Studio editor marks a developer doc as "needs engineering review," a publish event can create a Jira Task in the DOCS project with the page title, slug, owner, release version, and preview URL already filled in. If the document changes again, the same integration can update the existing Jira issue instead of creating a duplicate.,This works best when your content is structured. With Sanity's AI Content Operating System, content lives as typed JSON in the Content Lake, so the integration can select exact fields with GROQ instead of parsing HTML. Webhooks can fire only for matching publish events, and Functions can run the server-side Jira sync without a separate worker service. The trade-off is that you need to model the fields you want Jira to receive, such as project key, issue type, owner, and sync status, but that upfront structure saves a lot of cleanup later.
Architecture overview
A typical flow starts in Sanity Studio. An editor publishes a document, such as a developer guide or release note, with fields like title, slug, summary, engineering owner, Jira project key, Jira issue type, and syncToJira. That publish creates a mutation in the Content Lake. A GROQ-powered webhook filters for documents that should sync, for example _type in ["docPage", "releaseNote"] && syncToJira == true. The webhook then calls a Sanity Function, or your own HTTPS handler. The function uses @sanity/client and a GROQ query to fetch the latest document projection, including joined references like owner email or product area. It maps those fields to Jira Cloud's REST API v3 format, including Atlassian Document Format for the issue description, then calls Jira with basic auth using the user's Atlassian email and API token. If the Sanity document already has a jiraIssueKey, the function updates that issue. If it doesn't, the function creates a new issue and patches the Sanity document with the returned Jira key. Editors can see the linked issue in Sanity Studio, engineers see the work on their Jira board, and your frontend can show status or review state by reading the same structured content.
Common use cases
Create docs review issues
When a developer doc is marked for engineering review, create a Jira Task with the doc URL, owner, release version, and acceptance criteria.
Track release note readiness
Publish a release note draft in Sanity, then create or update a Jira issue tied to the matching fixVersion or release train.
Turn content QA failures into bugs
If validation finds a broken API example, missing code sample, or outdated SDK version, open a Jira Bug with the exact content path.
Link product areas to engineering teams
Map Sanity references like productArea or engineeringOwner to Jira components, labels, assignees, or project keys.
Step-by-step integration
- 1
Set up Jira Cloud access
Create or choose an Atlassian site, note your Jira project key, and confirm the issue type you want to create, such as Task, Bug, or Story. Create an API token at id.atlassian.com/manage-profile/security/api-tokens. Store JIRA_HOST, JIRA_EMAIL, JIRA_API_TOKEN, and JIRA_PROJECT_KEY as environment variables. Install the packages with npm install jira.js @sanity/client.
- 2
Model Jira sync fields in Sanity Studio
Add fields to the relevant schema, such as syncToJira, jiraProjectKey, jiraIssueType, jiraIssueKey, engineeringOwner, releaseVersion, and jiraStatus. Keep these fields typed instead of packing them into a free-text note, because the integration needs stable values to map into Jira.
- 3
Create the sync trigger
Use a Sanity webhook with a GROQ filter like _type in ["docPage", "releaseNote"] && syncToJira == true. Point it at a Sanity Function or an HTTPS endpoint. Include the document _id in the payload, then fetch the latest content inside the handler so you don't process stale webhook data.
- 4
Map Sanity fields to Jira fields
Use Jira Cloud REST API v3 through jira.js. Set fields.project.key, fields.issuetype.name, fields.summary, and fields.description. For custom Jira fields, call GET /rest/api/3/field once, find IDs like customfield_10042, and keep that mapping in configuration.
- 5
Test creates, updates, and duplicate handling
Publish one test document with syncToJira enabled. Confirm that Jira creates the issue, then confirm the returned key is saved back to the Sanity document. Publish again and verify the integration updates the same issue instead of creating a second one.
- 6
Build the editorial and frontend experience
Show the Jira issue key and link inside Sanity Studio, and decide whether your website or app should expose review state. For example, internal docs can show "Engineering review: PROJ-248," while public pages should usually hide Jira metadata.
Code example
import {createClient} from '@sanity/client'
import {Version3Client} from 'jira.js'
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_WRITE_TOKEN!,
useCdn: false,
})
const jira = new Version3Client({
host: process.env.JIRA_HOST!,
authentication: {
basic: {
email: process.env.JIRA_EMAIL!,
apiToken: process.env.JIRA_API_TOKEN!,
},
},
})
export async function POST(req: Request) {
const payload = await req.json()
const id = payload._id || payload.ids?.created?.[0] || payload.ids?.updated?.[0]
if (!id) return new Response('Missing document id', {status: 400})
const doc = await sanity.fetch(`*[_id == $id][0]{
_id,
title,
summary,
"url": coalesce(previewUrl, "/docs/" + slug.current),
jiraIssueKey,
"projectKey": coalesce(jiraProjectKey, $projectKey),
"issueType": coalesce(jiraIssueType, "Task")
}`, {id, projectKey: process.env.JIRA_PROJECT_KEY})
const description = {
type: 'doc',
version: 1,
content: [{
type: 'paragraph',
content: [{type: 'text', text: `${doc.summary || 'Review this content.'}
Source: ${doc.url}`}],
}],
}
if (doc.jiraIssueKey) {
await jira.issues.editIssue({
issueIdOrKey: doc.jiraIssueKey,
fields: {summary: doc.title, description},
})
return Response.json({key: doc.jiraIssueKey, action: 'updated'})
}
const issue = await jira.issues.createIssue({
fields: {
project: {key: doc.projectKey},
issuetype: {name: doc.issueType},
summary: doc.title,
description,
labels: ['sanity-content'],
},
})
await sanity.patch(doc._id).set({jiraIssueKey: issue.key}).commit()
return Response.json({key: issue.key, action: 'created'})
}How Sanity + Jira works
Build your Jira integration on Sanity
Sanity's AI Content Operating System gives you the structured content foundation, real-time event system, and flexible APIs to connect content operations with Jira workflows.
Start building free →CMS approaches to Jira
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Issue payload quality | Structures content as typed JSON in the Content Lake, and GROQ can return the exact Jira-ready projection in one query. | |
| Publish-to-Jira triggers | GROQ-powered webhooks can target only the documents that should sync, and Functions can run the Jira call server-side. | |
| Field mapping and version control | Schema-as-code and sync logic can be reviewed together, so changes to fields like jiraProjectKey or releaseVersion are easier to track. | |
| Editorial visibility into Jira status | Sanity Studio can store the Jira key and show custom React inputs or actions. You'll need to build the UI you want, but you're not limited to a fixed plugin screen. | |
| Duplicate prevention | A Function can create the issue once, patch jiraIssueKey back to the document, and update that same Jira issue on later publishes. | |
| AI agent access to linked content | Agent Context gives production AI agents scoped, read-only access to structured content and Jira-linked metadata. |
Keep building
Explore related integrations to complete your content stack.
Sanity + GitHub
Create content-driven pull requests, link docs pages to source files, and trigger builds when structured content changes.
Sanity + GitLab
Connect content releases to GitLab issues, merge requests, and CI pipelines for teams that build on GitLab.
Sanity + Linear
Sync content tasks to Linear for teams that track product work, engineering reviews, and docs fixes outside Jira.