Developer Tools8 min read

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.

Published April 30, 2026
01Overview

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.


02The case for integration

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.


03Architecture

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.


04Use cases

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.


05Implementation

Step-by-step integration

  1. 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. 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. 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. 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. 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. 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.


06Code

Code example

typescript
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'})
}

07Why Sanity

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 →

08Comparison

CMS approaches to Jira

CapabilityTraditional CMSSanity
Issue payload qualityStructures content as typed JSON in the Content Lake, and GROQ can return the exact Jira-ready projection in one query.
Publish-to-Jira triggersGROQ-powered webhooks can target only the documents that should sync, and Functions can run the Jira call server-side.
Field mapping and version controlSchema-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 statusSanity 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 preventionA 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 contentAgent Context gives production AI agents scoped, read-only access to structured content and Jira-linked metadata.

09Next 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 Jira and 200+ other tools.