Build a Jira Integration Chrome Extension: Complete 2025 Guide

21 min read

Build a Jira Integration Chrome Extension: Complete 2025 Guide

Build a Jira Integration Chrome Extension: Complete 2025 Guide

Jira is the backbone of project management for millions of development teams worldwide. Being able to quickly create issues, check ticket status, and access your Jira workflow directly from Chrome can dramatically improve productivity. In this comprehensive guide, we will walk you through building a production-ready Jira Integration Chrome Extension from scratch.

By the end of this article, you will have a fully functional extension that can authenticate with Jira, create issues, search for existing tickets, and provide a seamless user experience. Whether you are a developer looking to streamline your workflow or a business wanting to offer Jira integration as a feature, this guide has you covered.


Why Build a Jira Chrome Extension?

The demand for Jira integrations has never been higher. Development teams spend significant time switching between their browser and the Jira dashboard, context-switching that breaks focus and reduces productivity. A well-designed Jira Chrome Extension eliminates this friction by bringing Jira functionality directly into the browser.

Real-World Use Cases

A Jira Chrome Extension serves numerous practical purposes. Support teams can create bug tickets directly from customer emails without leaving their inbox. Developers can log issues while browsing documentation or code reviews. Project managers can quickly check ticket status without navigating away from their current task.

The most successful Jira extensions in the Chrome Web Store include features like one-click issue creation, inline time tracking, sprint analytics, and customizable notifications. Building an extension that solves one of these pain points well can attract thousands of active users.

Market Opportunity

Jira powers over 180,000 organizations globally, with millions of daily active users. The Chrome Web Store has several Jira-related extensions, but many are outdated, poorly maintained, or lack modern features. A well-built extension with excellent UX can quickly gain traction in this space.


Prerequisites and Setup

Before we start coding, let us ensure you have everything needed to build a Jira Chrome Extension. You will need a basic understanding of JavaScript, HTML, and CSS. Familiarity with REST APIs and OAuth authentication will be helpful but is not strictly required.

Development Environment

First, create a new directory for your extension and initialize the basic structure:

mkdir jira-extension
cd jira-extension
mkdir -p popup background icons

You will also need a Jira account. If you do not have one, you can create a free Jira Cloud account at atlassian.com. For development, we recommend using a free Jira site rather than a production instance.

Understanding Jira API

Jira provides a robust REST API that allows you to perform virtually any action available in the web interface. The API is organized around resources like issues, projects, users, and boards. For our extension, we will focus on authentication, issue creation, and issue search.

Jira Cloud uses OAuth 2.0 for third-party authentication. This is more secure than API tokens and follows modern security best practices. However, for personal or internal use, API tokens remain a viable option.


Project Structure

A well-organized Chrome Extension follows a clear file structure. Here is what our Jira extension will look like:

jira-extension/
├── manifest.json
├── popup/
│   ├── popup.html
│   ├── popup.js
│   └── popup.css
├── background/
│   └── background.js
├── icons/
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
└── utils/
    └── jira-api.js

The manifest.json defines the extension configuration. The popup directory contains the UI that appears when users click the extension icon. The background script handles long-running tasks and communication between components. The utils directory contains helper functions for API calls.


Creating the Manifest

The manifest.json is the heart of every Chrome Extension. For our Jira integration, we need to configure permissions, declare the extension type, and define the popup interface.

Create a manifest.json file with the following content:

{
  "manifest_version": 3,
  "name": "Jira Quick Create",
  "version": "1.0.0",
  "description": "Create Jira issues instantly from your browser",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "https://*.atlassian.net/*"
  ],
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background/background.js"
  }
}

The host_permissions section is critical. It grants our extension access to Jira domains, which is required for making API calls. The storage permission allows us to save user credentials and settings locally.


The popup is the main user interface of our extension. It appears when users click the extension icon in Chrome. Let us create a clean, functional popup that allows users to create Jira issues quickly.

HTML Structure

Create popup/popup.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Jira Quick Create</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>Jira Quick Create</h1>
      <div id="auth-status" class="auth-status"></div>
    </header>
    
    <main id="main-content">
      <form id="issue-form">
        <div class="form-group">
          <label for="project-key">Project Key</label>
          <input type="text" id="project-key" placeholder="e.g., PROJ" required>
        </div>
        
        <div class="form-group">
          <label for="issue-type">Issue Type</label>
          <select id="issue-type" required>
            <option value="Bug">Bug</option>
            <option value="Task">Task</option>
            <option value="Story">Story</option>
            <option value="Epic">Epic</option>
          </select>
        </div>
        
        <div class="form-group">
          <label for="issue-summary">Summary</label>
          <input type="text" id="issue-summary" placeholder="Issue summary" required>
        </div>
        
        <div class="form-group">
          <label for="issue-description">Description</label>
          <textarea id="issue-description" rows="4" placeholder="Describe the issue..."></textarea>
        </div>
        
        <button type="submit" id="create-btn">Create Issue</button>
      </form>
      
      <div id="result" class="result hidden"></div>
    </main>
    
    <div id="login-view" class="hidden">
      <p>Please connect your Jira account to get started.</p>
      <button id="connect-btn">Connect Jira</button>
    </div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

Styling the Popup

Create popup/popup.css to make the popup look professional:

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 350px;
  background: #ffffff;
  color: #172b4d;
}

.container {
  padding: 16px;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #dfe1e6;
}

h1 {
  font-size: 16px;
  font-weight: 600;
}

.auth-status {
  font-size: 12px;
  padding: 4px 8px;
  border-radius: 4px;
}

.auth-status.connected {
  background: #e3fcef;
  color: #006644;
}

.auth-status.disconnected {
  background: #ffebe6;
  color: #de350b;
}

.form-group {
  margin-bottom: 12px;
}

label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  margin-bottom: 4px;
  color: #5e6c84;
}

input, select, textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #dfe1e6;
  border-radius: 4px;
  font-size: 14px;
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: #0052cc;
}

button {
  width: 100%;
  padding: 10px;
  background: #0052cc;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;
}

button:hover {
  background: #0747a6;
}

button:disabled {
  background: #b3bac5;
  cursor: not-allowed;
}

.hidden {
  display: none;
}

.result {
  margin-top: 12px;
  padding: 12px;
  border-radius: 4px;
}

.result.success {
  background: #e3fcef;
  border: 1px solid #b6e2c7;
}

.result.error {
  background: #ffebe6;
  border: 1px solid #ffb3a7;
}

.result a {
  color: #0052cc;
  font-weight: 500;
}

Implementing the Jira API Client

The API client handles all communication with Jira. We will create a utility module that encapsulates authentication and API calls. Create utils/jira-api.js:

const JIRA_API_VERSION = '3';

class JiraClient {
  constructor() {
    this.baseUrl = null;
    this.email = null;
    this.apiToken = null;
  }

  async initialize() {
    const storage = await chrome.storage.local.get(['jiraUrl', 'jiraEmail', 'jiraToken']);
    this.baseUrl = storage.jiraUrl;
    this.email = storage.jiraEmail;
    this.apiToken = storage.jiraToken;
    return this.isAuthenticated();
  }

  isAuthenticated() {
    return !!(this.baseUrl && this.email && this.apiToken);
  }

  async authenticate(url, email, token) {
    this.baseUrl = url.replace(/\/$/, '');
    this.email = email;
    this.apiToken = token;

    await chrome.storage.local.set({
      jiraUrl: this.baseUrl,
      jiraEmail: this.email,
      jiraToken: this.apiToken
    });

    try {
      await this.getProjects();
      return true;
    } catch (error) {
      this.clearAuth();
      throw new Error('Authentication failed. Please check your credentials.');
    }
  }

  clearAuth() {
    this.baseUrl = null;
    this.email = null;
    this.apiToken = null;
    chrome.storage.local.remove(['jiraUrl', 'jiraEmail', 'jiraToken']);
  }

  async request(endpoint, method = 'GET', body = null) {
    if (!this.isAuthenticated()) {
      throw new Error('Not authenticated');
    }

    const url = `${this.baseUrl}/rest/api/${JIRA_API_VERSION}${endpoint}`;
    const headers = {
      'Authorization': `Basic ${btoa(`${this.email}:${this.apiToken}`)}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };

    const options = {
      method,
      headers
    };

    if (body) {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(url, options);
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.errorMessages?.[0] || `HTTP ${response.status}`);
    }

    return response.json();
  }

  async getProjects() {
    return this.request('/project');
  }

  async createIssue(projectKey, issueType, summary, description) {
    const issueData = {
      fields: {
        project: {
          key: projectKey
        },
        summary: summary,
        description: {
          type: 'doc',
          version: 1,
          content: [{
            type: 'paragraph',
            content: [{
              type: 'text',
              text: description || ''
            }]
          }]
        },
        issuetype: {
          name: issueType
        }
      }
    };

    return this.request('/issue', 'POST', issueData);
  }

  async searchIssues(jql) {
    return this.request(`/search?jql=${encodeURIComponent(jql)}&maxResults=10`);
  }

  async getIssue(issueKey) {
    return this.request(`/issue/${issueKey}`);
  }
}

const jiraClient = new JiraClient();

This API client handles authentication storage, makes authenticated requests to Jira, and provides convenient methods for common operations like creating issues and searching.


Now we need to connect the popup UI to our API client. Create popup/popup.js:

document.addEventListener('DOMContentLoaded', async () => {
  const loginView = document.getElementById('login-view');
  const mainContent = document.getElementById('main-content');
  const authStatus = document.getElementById('auth-status');
  const issueForm = document.getElementById('issue-form');
  const resultDiv = document.getElementById('result');
  const createBtn = document.getElementById('create-btn');

  let isAuthenticated = false;

  async function checkAuth() {
    await jiraClient.initialize();
    isAuthenticated = jiraClient.isAuthenticated();
    updateUI();
  }

  function updateUI() {
    if (isAuthenticated) {
      loginView.classList.add('hidden');
      mainContent.classList.remove('hidden');
      authStatus.textContent = 'Connected';
      authStatus.className = 'auth-status connected';
    } else {
      loginView.classList.remove('hidden');
      mainContent.classList.add('hidden');
      authStatus.textContent = 'Not Connected';
      authStatus.className = 'auth-status disconnected';
    }
  }

  issueForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const projectKey = document.getElementById('project-key').value.trim();
    const issueType = document.getElementById('issue-type').value;
    const summary = document.getElementById('issue-summary').value.trim();
    const description = document.getElementById('issue-description').value.trim();

    if (!projectKey || !summary) {
      showResult('Please fill in all required fields.', 'error');
      return;
    }

    createBtn.disabled = true;
    createBtn.textContent = 'Creating...';

    try {
      const result = await jiraClient.createIssue(projectKey, issueType, summary, description);
      showResult(`Issue created successfully! <a href="${jiraClient.baseUrl}/browse/${result.key}" target="_blank">${result.key}</a>`, 'success');
      issueForm.reset();
    } catch (error) {
      showResult(`Error: ${error.message}`, 'error');
    } finally {
      createBtn.disabled = false;
      createBtn.textContent = 'Create Issue';
    }
  });

  function showResult(message, type) {
    resultDiv.innerHTML = message;
    resultDiv.className = `result ${type}`;
    resultDiv.classList.remove('hidden');
  }

  document.getElementById('connect-btn').addEventListener('click', async () => {
    const url = prompt('Enter your Jira URL (e.g., https://yourcompany.atlassian.net):');
    if (!url) return;
    
    const email = prompt('Enter your Jira email:');
    if (!email) return;
    
    const token = prompt('Enter your Jira API token:\n\nYou can generate one at: https://id.atlassian.com/manage-profile/security/api-tokens');
    if (!token) return;

    try {
      await jiraClient.authenticate(url, email, token);
      isAuthenticated = true;
      updateUI();
    } catch (error) {
      showResult(error.message, 'error');
    }
  });

  await checkAuth();
});

The popup handles user interactions, form submissions, and displays results. When users click Connect Jira, they are prompted for their Jira credentials, which are then stored securely in Chrome’s local storage.


Background Service Worker

The background script handles events that occur in the background, such as installation, updates, or messages from content scripts. While our current implementation primarily uses the popup, adding a background worker provides extensibility for future features.

Create background/background.js:

chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('Jira Quick Create extension installed');
  } else if (details.reason === 'update') {
    console.log('Jira Quick Create extension updated');
  }
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'createIssue') {
    handleCreateIssue(request.data)
      .then(result => sendResponse({ success: true, data: result }))
      .catch(error => sendResponse({ success: false, error: error.message }));
    return true;
  }
});

async function handleCreateIssue(data) {
  await jiraClient.initialize();
  if (!jiraClient.isAuthenticated()) {
    throw new Error('Not authenticated');
  }
  return jiraClient.createIssue(data.projectKey, data.issueType, data.summary, data.description);
}

Testing Your Extension

Now that we have built all the components, it is time to test the extension. Follow these steps to load your extension in Chrome:

  1. Open Chrome and navigate to chrome://extensions/
  2. Enable “Developer mode” in the top right corner
  3. Click “Load unpacked” and select your extension directory
  4. The extension icon should appear in your Chrome toolbar

To test the extension:

  1. Click the extension icon
  2. Click “Connect Jira” and enter your Jira credentials
  3. Enter a project key, issue type, summary, and description
  4. Click “Create Issue” and verify the issue appears in Jira

Make sure to test with different project keys and issue types. Verify that error handling works correctly when providing invalid credentials or non-existent projects.


Best Practices and Security Considerations

When building a Jira extension that handles authentication, security is paramount. Here are essential best practices to follow:

Secure Credential Storage

Never store credentials in plain text or localStorage, which is accessible to content scripts. Chrome’s storage API provides better security, but for production extensions, consider using the identity API with OAuth 2.0 for the most secure authentication flow.

HTTPS Only

Always use HTTPS for API requests. Jira Cloud requires secure connections, and Chrome extensions are subject to Content Security Policy restrictions that prevent HTTP requests in most contexts.

Error Handling

Implement comprehensive error handling throughout your extension. Users should receive clear, actionable error messages when something goes wrong. Handle network errors, authentication failures, and API rate limits gracefully.

User Experience

Keep the popup lightweight and fast. Users expect instant responses from browser extensions. Lazy-load non-critical data and use loading states to provide feedback during async operations.


Publishing to the Chrome Web Store

Once your extension is tested and working, you can publish it to the Chrome Web Store. Here is the process:

  1. Create a developer account at the Chrome Web Store Developer Dashboard
  2. Package your extension as a ZIP file
  3. Upload the ZIP and fill in the store listing details
  4. Submit for review (typically takes 1-3 days)
  5. Once approved, your extension will be live

For the store listing, use relevant keywords like “Jira,” “issue creator,” “Atlassian,” and “productivity” in your title and description to improve discoverability.


Conclusion

You now have a complete, production-ready Jira Integration Chrome Extension. The extension demonstrates key concepts including Manifest V3 configuration, secure API client implementation, popup UI design, and Chrome storage for persistence.

This foundation can be extended with many additional features: issue search and filtering, time tracking integration, sprint analytics, customizable keyboard shortcuts, and more. The Jira API provides extensive capabilities beyond what we covered in this guide.

Building a Jira Chrome Extension is an excellent project that combines real-world utility with modern web development practices. Users love extensions that save them time and streamline their workflows, making this a rewarding project whether you are building for personal use, your team, or a broader audience.

Start building today, and transform how you and your team interact with Jira!


Next Steps

To take your Jira extension to the next level, consider adding these advanced features:

  • OAuth 2.0 Authentication: Implement proper OAuth flow for better security and user trust
  • Keyboard Shortcuts: Allow users to trigger issue creation with global shortcuts
  • Context Menus: Add Jira options to the right-click menu
  • Issue Templates: Create reusable templates for common issue types
  • Multiple Jira Instances: Support connecting to multiple Jira sites
  • Offline Support: Cache recent issues and queue actions when offline

The Chrome Extensions documentation and Jira API documentation are excellent resources for continuing your learning journey.

No previous article
No next article