Skip to content

Latest commit

 

History

300 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌐 English Β· Русский

jira.js β€” Jira REST API client for JavaScript and TypeScript

NPM version NPM downloads per month build status license TypeScript Node.js

jira.js β€” Jira REST API client for Node.js, TypeScript & browsers

JavaScript / TypeScript library for Node.js and browsers to interact with Atlassian Jira APIs

About

Jira.js is a TypeScript client for the Atlassian Jira REST APIs β€” Jira Cloud and self-hosted Jira Data Center β€” for Node.js and browsers. It covers eleven surfaces, in eight groups:

6.0 is a rewrite, not a refresh. npm install jira.js now installs 6.x. Read MIGRATION.md before upgrading β€” it says plainly who should stay on jira.js@5, which is supported until the end of 2026.

Key Features

  • βœ… Type-Safe: every endpoint, parameter and model is typed, and src/ ships with the package so "go to definition" lands on the real source
  • βœ… Validated at runtime: responses are checked against a schema, and drift is reported by field instead of surfacing as undefined three frames later
  • βœ… Promise-based: clean, async/await-friendly methods throughout
  • βœ… Tree-Shakable: import a single endpoint function instead of a whole client
  • βœ… Universal: one ESM build for Node.js 22+ and modern browsers
  • βœ… One dependency: zod, and nothing else
  • βœ… Typed errors: a hierarchy with predicates that survive bundling, minification and duplicate installs
  • βœ… OAuth 2.0 (3LO): automatic refresh, single-flight, 401 retry and cloud id resolution

Built for Jira integrations, automation, webhook handlers, CI/CD pipelines and browser-based tools.

Table of Contents

Getting Started

Installation

Requires Node.js 22 or newer. The package is ESM-only β€” there is no CommonJS build. TypeScript users need 5.7 or newer: the declarations name ArrayBufferView, which earlier compilers read as a different type.

# Using npm
npm install jira.js

# Using yarn
yarn add jira.js

# Using pnpm
pnpm add jira.js

TypeScript users: type definitions are included - no additional @types package needed.

Quick Example

import { createCloudClient } from 'jira.js';

const jira = createCloudClient({
  host: 'https://your-domain.atlassian.net',
  auth: {
    type: 'basic',
    email: 'your@email.com',
    apiToken: 'YOUR_API_TOKEN', // Create one: https://id.atlassian.com/manage-profile/security/api-tokens
  },
});

const project = await jira.projects.getProject({ projectIdOrKey: 'YOUR_PROJECT_KEY' });

const issue = await jira.issues.createIssue({
  fields: {
    summary: 'Hello Jira.js!',
    issuetype: { name: 'Task' },
    project: { key: project.key },
  },
});

console.log(`Issue created: ${issue.key}`);

host is the bare site URL β€” the API path belongs to the request, not here.

Need more than one surface? Build the client once and hand it to each factory. Under OAuth 2.0 this matters: two clients mean two token states, and since Atlassian rotates the refresh token on every refresh, whichever refreshes first invalidates the other's copy.

import { createClient } from 'jira.js/core';
import { createAgileClient, createCloudClient } from 'jira.js';

const client = createClient({ host, auth });

const jira = createCloudClient(client);
const agile = createAgileClient(client);

Documentation

πŸ“š Full API reference, guides, and examples available at: https://mrrefactoring.github.io/jira.js/

The documentation includes:

  • Complete API reference for all endpoints
  • TypeScript examples and code samples
  • Authentication guides
  • Error handling patterns
  • Best practices and tips

Supported APIs

  • Jira Cloud platform API: issues, projects, users, fields, workflows, schemes
  • Jira Software (Agile) API: sprint management, boards, backlogs, agile workflows
  • Jira Service Management API: request handling, queues, customers, organizations
  • Jira Data Center API: self-hosted Jira, /rest/api/2 plus the Agile endpoints, from one createServerClient
  • Jira Service Management Data Center API: self-hosted requests, queues and organizations, from createServiceDeskServerClient
  • Assets API: objects, schemas, types and AQL β€” createAssetsClient on Cloud, createAssetsServerClient self-hosted
  • Teams API: teams, their members and external links, at organization level β€” createTeamsClient
  • Organization administration: directories, users, groups, domains, policies and SCIM provisioning above the site β€” createAdminClient, createUserManagementClient, createUserProvisioningClient
  • Webhook types: the events, payloads and headers Jira posts to you β€” jira.js/webhooks, types only, no client

There is one Cloud platform surface, generated from Jira's v3 specification. Version2Client and Version3Client are gone β€” the difference between them was never the endpoints, it was rich text. Rich-text fields still accept a wiki-markup string: that write is routed through Jira's v2 endpoint, which parses the markup server-side, and the result is read back so what you get is a real Atlassian Document Format document.

// Wiki markup β€” still works, still formats
await jira.issueComments.addComment({
  issueIdOrKey: 'PROJ-1',
  body: 'h2. Heading\n\n*bold* and {code}inline{code}',
});

Reads always come back as a document, never as a string. None of that applies to Data Center: it has no ADF, and createServerClient hands you wiki markup as a plain string on read just as it takes one on write β€” a comment's body is a string, not a document.

Usage

Authentication

Authentication is the auth field β€” a discriminated union on type.

Email and API Token

  1. Create an API token: https://id.atlassian.com/manage-profile/security/api-tokens
  2. Configure the client:
const jira = createCloudClient({
  host: 'https://your-domain.atlassian.net',
  auth: { type: 'basic', email: 'YOUR@EMAIL.ORG', apiToken: 'YOUR_API_TOKEN' },
});

Bearer Token

When something else already obtained an access token and you manage its lifetime yourself:

const jira = createCloudClient({
  host: 'https://your-domain.atlassian.net',
  auth: { type: 'bearer', token: 'YOUR_ACCESS_TOKEN' },
});

Nothing is refreshed for you here β€” when the token expires, requests fail with AuthError.

OAuth 2.0

jira.js supports the full Atlassian OAuth 2.0 (3LO) flow. Provide refresh credentials and the client refreshes the access token before expiry (and on 401), collapses concurrent refreshes into one call, persists the rotated refresh token via onTokenRefresh, and routes requests through the API gateway (https://api.atlassian.com/ex/jira/{cloudId}) β€” so no host is needed. clientSecret and refresh are server-side only.

const jira = createCloudClient({
  // no `host` β€” the cloudId is resolved automatically (pass `siteUrl` or `cloudId` to pin it)
  auth: {
    type: 'oauth2',
    accessToken: 'CURRENT_ACCESS_TOKEN',
    refreshToken: 'CURRENT_REFRESH_TOKEN',
    clientId: 'YOUR_CLIENT_ID',
    clientSecret: 'YOUR_CLIENT_SECRET',
    expiresAt: Date.now() + 3600 * 1000, // optional; epoch milliseconds
    onTokenRefresh: async ({ accessToken, refreshToken, expiresAt }) => {
      await saveTokens({ accessToken, refreshToken, expiresAt }); // persist the rotated tokens
    },
  },
});

Persisting the rotated refresh token is not optional β€” Atlassian invalidates the previous one on every refresh.

jira.js also exports stateless helpers for the authorization-code flow β€” generateAuthorizationUrl, exchangeAuthorizationCode, refreshOAuth2Token, getAccessibleResources, parseCallbackUrl. See the step-by-step OAuth 2.0 guide.

Jira Data Center

createServerClient takes a different set of strategies. Data Center has no API tokens, so the Cloud pair of email and apiToken does not apply β€” it compiles, and then answers 401. Cloud OAuth 2.0 (3LO) does not apply either: it resolves a cloud id and routes through api.atlassian.com, so a client built with it never reaches the host you passed.

// A personal access token β€” available since Jira 8.14, and the only way in on a default Jira 11 instance
const jira = createServerClient({
  host: 'https://jira.your-company.com',
  auth: { type: 'bearer', token: 'YOUR_PERSONAL_ACCESS_TOKEN' },
});

// A local account β€” `username`, not `email`. Jira 11 disables basic authentication by default.
const jiraBasic = createServerClient({
  host: 'https://jira.your-company.com',
  auth: { type: 'basic', username: 'jdoe', password: 'hunter2' },
});

OAuth 2.0 against the instance's own authorization server is { type: 'oauth2Server', ... }, with generateServerAuthorizationUrl, exchangeServerAuthorizationCode and refreshServerOAuth2Token beside it. See the Data Center guide.

JWT (Atlassian Connect) is not supported in 6.0 and has no replacement. If you authenticate Connect installations with a shared secret, stay on jira.js@5 β€” see MIGRATION.md. Atlassian Connect itself is reaching end of support in Q4 2026.

Error Handling

Every failure arrives as one of the library's own error types, each with a predicate:

import { isNotFoundError, isRateLimitError } from 'jira.js';

try {
  await jira.issues.getIssue({ issueIdOrKey: 'INVALID-123' });
} catch (error) {
  if (isNotFoundError(error)) return null;

  if (isRateLimitError(error) && error.retryAfterMs) {
    await new Promise(resolve => setTimeout(resolve, error.retryAfterMs));
  }

  throw error;
}
Error When Extra
ApiError Any non-2xx; base of the ones below status, statusText, body
AuthError 401, or any status Jira answered while refusing the credentials status is what was on the wire
ScopeError 401, token lacks the scope
ForbiddenError 403
NotFoundError 404
RateLimitError 429 retryAfterMs
ServerError 5xx
NetworkError Request never completed code
OAuthError The token flow failed
ConfigError Impossible client configuration
SchemaMismatchError 2xx of the wrong shape report

An aborted request is the one failure that is not one of these: the reason you passed to abort() is rethrown exactly as it is, so error.name === 'AbortError' and error === signal.reason both hold.

Use the predicates rather than instanceof: they read a branded symbol instead of walking the prototype chain, so they keep working when a bundler splits chunks, when minification renames classes, and when two copies of the package end up in one node_modules.

Retries are off by default. retry: { maxAttempts, initialDelayMs, backoffFactor } opts in for network errors and 502/503/504 only β€” never 4xx, never other 5xx.

A dead token does not always arrive as a 401. Around a quarter of Jira's operations can be reached anonymously, and on those an expired or revoked API token does not fail the request β€” Jira serves it as the anonymous user and says so only in the X-Seraph-LoginReason header. What you get back is a well-formed, successful response containing whatever an anonymous visitor is allowed to see, which for most sites is nothing:

// With a dead token, before 6.3: no error, and an empty list.
const projects = await jira.projects.searchProjects();
// { total: 0, isLast: true, values: [] }

The client now reads that header and throws AuthError whenever the credentials were refused, whatever status came with it. error.status records the status that actually arrived β€” 200 in the case above β€” rather than a 401 that never happened.

Cancelling a Request

Every method takes an optional AbortSignal as its last argument. It reaches fetch, and it also cuts short any retry back-off the client is waiting out:

await jira.issues.getIssue({ issueIdOrKey: 'PROJ-1' }, { signal: AbortSignal.timeout(5_000) });

// Operations that take no parameters take the options in their place
await jira.announcementBanner.getBanner({ signal });

// ...and so do the ones whose parameters are all optional, after an empty object
await jira.projects.searchProjects({}, { signal });

The reason is rethrown untouched rather than wrapped, so a TimeoutError from AbortSignal.timeout() stays a TimeoutError, and a reason of your own comes back as the object you passed.

Custom fetch

The transport has one seam: the fetch it calls. Replace it to log, to trace, to route through a proxy, or to record fixtures β€” the client hands it the URL and the RequestInit it built, including the headers it derived from auth:

const jira = createCloudClient({
  host,
  auth,
  fetch: async (url, init) => {
    const started = performance.now();
    const response = await fetch(url, init);

    console.log(`${init.method ?? 'GET'} ${url} β†’ ${response.status} in ${Math.round(performance.now() - started)}ms`);

    return response;
  },
});
import { fetch as undiciFetch, ProxyAgent } from 'undici';

const dispatcher = new ProxyAgent(process.env.HTTPS_PROXY!);

const jira = createCloudClient({
  host,
  auth,
  fetch: (url, init) => undiciFetch(url, { ...init, dispatcher }),
});

The OAuth 2.0 token and cloud-id calls go through it too, so a proxy covers the whole flow rather than working until the first refresh. The flip side: a wrapper that logs request bodies will see client_secret and refresh_token on the token call.

headers is the other way in, for something constant: createCloudClient({ host, auth, headers: { 'X-Trace-Id': traceId } }). Per-request headers win over it, and it wins over the Authorization header the client derives β€” so setting Authorization there silently replaces auth, refresh included.

Response Validation

Every response is checked against a schema. When one does not match, the library does not throw: the body comes back unvalidated and the problem is reported once per distinct field, on stderr.

[jira.js] GET /rest/api/3/project/{projectIdOrKey}/role answered with something the schema
does not describe: at `10002`, expected string, got number. The response is returned
unvalidated.

The shapes Jira sends depend on things a library cannot see β€” your site's locale, which features are on, team-managed versus company-managed projects, an enum Atlassian grew this week. A schema here being wrong about one of those is not your bug and should not stop your program.

const jira = createCloudClient({
  host,
  auth,
  onSchemaMismatch: 'warn', // 'silent' | 'throw' | (report) => void
});

Use 'throw' in a test suite, where a mismatch is the thing under test. The report names field paths and types and never the values at them β€” it is meant to be pasted into an issue. See the Response Validation guide.

API Structure

Access endpoints using the client.<group>.<method> pattern:

// Get all projects
const projects = await jira.projects.searchProjects();

// Create a sprint (Agile surface)
const sprint = await agile.sprint.createSprint({ name: 'Q4 Sprint' });

Available API groups:

πŸ”½ Agile Cloud API
πŸ”½ Jira Cloud platform API
πŸ”½ Service Desk API

See the full endpoint reference in the API documentation.

Tree Shaking & Bundle Optimization

The package declares "sideEffects": false and ships one module per source file, so a bundler can drop everything you do not import.

createCloudClient is convenient and expensive: it wires up every endpoint on the platform surface. For a bundle that calls a handful of endpoints, compose the client yourself from the flat functions instead:

import { createClient } from 'jira.js/core';
import { getIssue, createIssue } from 'jira.js/cloud';
import { createSprint } from 'jira.js/agile';

const client = createClient({
  host: 'https://your-domain.atlassian.net',
  auth: { type: 'basic', email, apiToken },
});

const issue = await getIssue(client, { issueIdOrKey: 'KEY-1' });

Every function takes the client as its first argument β€” the same client the factories build, so the two styles mix freely.

Import Contents
jira.js The eleven factories, error types and predicates, OAuth helpers
jira.js/core createClient, transport, errors, OAuth, multipart helpers
jira.js/cloud Platform API functions and response types
jira.js/cloud/models Platform API response types on their own
jira.js/cloud/parameters Platform API request parameter types
jira.js/agile Agile API functions and response types
jira.js/agile/models Agile API response types on their own
jira.js/agile/parameters Agile API request parameter types
jira.js/serviceDesk Service Management functions and response types
jira.js/serviceDesk/models Service Management response types on their own
jira.js/serviceDesk/parameters Service Management request parameter types
jira.js/server Data Center functions and response types
jira.js/server/models Data Center response types on their own
jira.js/server/parameters Data Center request parameter types
jira.js/serviceDeskServer Service Management Data Center functions and response types
jira.js/serviceDeskServer/models Service Management Data Center response types on their own
jira.js/serviceDeskServer/parameters Service Management Data Center request parameter types
jira.js/assetsServer Assets Data Center functions and response types
jira.js/assetsServer/models Assets Data Center response types on their own
jira.js/assetsServer/parameters Assets Data Center request parameter types
jira.js/assets Assets Cloud functions and response types
jira.js/assets/models Assets Cloud response types on their own
jira.js/assets/parameters Assets Cloud request parameter types
jira.js/teams Teams functions and response types
jira.js/teams/models Teams response types on their own
jira.js/teams/parameters Teams request parameter types
jira.js/admin Organization API functions and response types
jira.js/admin/models Organization API response types on their own
jira.js/admin/parameters Organization API request parameter types
jira.js/userManagement User management functions and response types
jira.js/userManagement/models User management response types on their own
jira.js/userManagement/parameters User management request parameter types
jira.js/userProvisioning SCIM provisioning functions and response types
jira.js/userProvisioning/models SCIM provisioning response types on their own
jira.js/userProvisioning/parameters SCIM provisioning request parameter types
jira.js/webhooks The events, payloads and headers Jira posts to you, and the signature check
jira.js/browser Prebuilt browser bundle

The surface subpaths carry the response types alongside the functions, so a type-only import costs nothing at runtime. Request parameter types sit one level down, because a parameter and a model occasionally share a name:

import type { Issue } from 'jira.js/cloud';
import type { GetIssue } from 'jira.js/cloud/parameters';

The eleven surfaces are not re-exported from the root β€” they collide on a handful of names, so import from the one you mean.

Deep imports need an exports-aware resolver: moduleResolution: "bundler", "node16" or "nodenext". The legacy "node" resolution cannot see them, and cannot load an ESM-only package either.

Schemas are the bulk of the package β€” each response type carries the schema it is validated against β€” so the saving is roughly proportional to how much of the API you leave out.

Use Cases

Jira.js is perfect for:

  • πŸ”„ CI/CD Integration: Automate issue creation and updates in your deployment pipelines
  • πŸ€– Automation Scripts: Build custom automation for Jira workflows and processes
  • πŸ“Š Reporting & Analytics: Extract and analyze Jira data for custom dashboards
  • πŸ”— Webhook Handlers: Process Jira webhooks and integrate with external systems
  • πŸ› οΈ Custom Tools: Build admin tools, migration scripts, and custom Jira applications
  • πŸ“± Browser Apps: Create browser-based Jira management interfaces
  • πŸ”Œ Third-Party Integrations: Connect Jira with other services and platforms

Common Questions (FAQ)

Q: Does this cover Assets?
A: Yes, since 6.3, on both deployments. createAssetsClient covers the Assets Cloud REST API β€” it takes a workspaceId and its own configuration, because Assets answers on api.atlassian.com rather than on your site. createAssetsServerClient covers the self-hosted one and takes the same client as every other Data Center surface. See the Assets guide.

Q: Does this work with Jira Server/Data Center?
A: Yes, since 6.3. Use createServerClient for self-hosted Jira 10.0 and later β€” see the Data Center guide. It is a separate surface from the Cloud one, because the two APIs differ in more than their host. Jira 9.x is not supported: Atlassian never published a specification for it, and the line reached end of life in June 2026.

Q: Why do I get a 400 when I set assignee while creating an issue?
A: Because the field is not on that project's create screen β€” the library sends fields through untouched. GET /rest/api/3/issue/createmeta/{projectKey}/issuetypes/{issueTypeId} lists what the project accepts on create; if assignee is missing from it, either add it to the create screen in the project settings or assign afterwards with issues.assignIssue({ issueIdOrKey, accountId }). The shape itself is { fields: { assignee: { id: accountId } } }, and Jira names the real cause in the response body, which reaches you as error.body. Creating an issue as deliberately unassigned is not something the API supports at all.

Q: Where do I get my cloudId or orgId?
A: From getTenantContext, since Atlassian publishes no REST endpoint for either. const { cloudId, orgId } = await getTenantContext(client); β€” it takes the client you already built and asks the GraphQL gateway, which is the documented way. orgId is the one the Teams API takes, and it names the organization your site belongs to rather than the site. Neither changes, so resolve once at start-up and keep the answer. Not available under OAuth 2.0 (3LO) β€” see the Tenant Context guide.

Q: How do I name the type of a call's parameters?
A: Import it from the surface's parameters subpath: import type { CreateIssue } from 'jira.js/cloud/parameters';. Response types come from the surface itself β€” import type { Issue } from 'jira.js/cloud'; β€” because a parameter and a model occasionally share a name.

Q: Is TypeScript required?
A: No, but TypeScript is fully supported with comprehensive type definitions. You can use Jira.js with plain JavaScript too.

Q: Can I use this in the browser?
A: Yes. The package is browser-safe throughout and ships a prebuilt bundle at jira.js/browser. Calling Jira directly from a page is usually blocked by CORS and exposes credentials to anyone with devtools, so this suits extensions, Forge apps and proxied setups rather than putting an API token in a web app.

Q: How do I handle authentication?
A: Email + API token, a bearer token, or OAuth 2.0 (3LO) with automatic refresh. See the Authentication section above.

Q: Can I still use CommonJS?
A: No. 6.0 is ESM-only β€” require('jira.js') does not work. From a CommonJS module, use a dynamic await import('jira.js'), or stay on jira.js@5.

Q: What happened to JWT / Atlassian Connect?
A: It was removed in 6.0 and has no replacement. Stay on jira.js@5, which receives security and critical fixes until the end of 2026 β€” when Atlassian Connect itself reaches end of support.

Q: A response failed validation. Is that a bug in my code?
A: Usually not. It means the schema shipped here is behind what your Jira actually sends. By default the body is returned anyway and the problem is reported once β€” please open an issue with the report, which contains field paths and types and no values from your data.

Other Products

Explore our other Atlassian integration libraries:

Contributing

Contributions are welcome. One thing to know first: most of src/ β€” every api, models and parameters directory, and src/core β€” is generated from Atlassian's OpenAPI documents by apis-code-gen, and a change made there is overwritten on the next regeneration. CONTRIBUTING.md says which repository a fix belongs in and how to run the checks. For major changes, open an issue first.

License

MIT License Β© MrRefactoring
See LICENSE for details.

About

Modern Jira REST API client for JavaScript & TypeScript β€” Jira Cloud API v2/v3, Agile & Service Desk. Type-safe, tree-shakable, works in Node.js and browsers (ESM/CJS).

Topics

Resources

Contributing

Stars

493 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages