Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

258 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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 Cloud REST APIs, for Node.js and browsers. It covers three surfaces:

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.

# 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

There is one 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.

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.

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

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.

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 three factories, error types and predicates, OAuth helpers
jira.js/core createClient, transport, errors, OAuth, multipart helpers
jira.js/cloud Platform API functions, parameters and response types
jira.js/agile Agile API functions, parameters and response types
jira.js/serviceDesk Service Management functions, parameters and response types
jira.js/browser Prebuilt browser bundle

The surface subpaths carry the types alongside the functions, so a type-only import costs nothing at runtime:

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

The three 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 work with Jira Server/Data Center?
A: No, Jira.js is designed specifically for Jira Cloud. For on-premise Jira, consider using the REST API directly.

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! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

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

Stars

492 stars

Watchers

4 watching

Forks

Releases

Used by

Contributors

Languages