# Work with Jira Issues

> For the complete documentation index, see [llms.txt](https://docs.redpanda.com/llms.txt). Component-specific: [connect-full.txt](https://docs.redpanda.com/connect-full.txt)

---
title: Work with Jira Issues
latest-connect-version: 4.93.0
latest-operator-version: v26.1.4
latest-console-tag: v3.7.3
latest-redpanda-tag: v26.1.9
docname: jira
page-component-name: connect
page-version: master
page-component-version: master
page-component-title: Connect
page-relative-src-path: jira.adoc
page-edit-url: https://github.com/redpanda-data/rp-connect-docs/edit/main/modules/cookbooks/pages/jira.adoc
description: Learn how to use the Jira processor as both an input and output in Redpanda Connect.
page-git-created-date: "2025-11-21"
page-git-modified-date: "2026-01-05"
---

<!-- Source: https://docs.redpanda.com/connect/cookbooks/jira.md -->

The Jira processor enables querying Jira issues using JQL (Jira Query Language) and returning structured data. Unlike traditional connectors, Jira is implemented as a **processor only**, which can be used as both an input (with `generate`) and an output (with `drop`).

## [](#prerequisites)Prerequisites

### [](#redpanda-cli)Redpanda CLI

Install the Redpanda CLI (`rpk`) to run Redpanda Connect. See [Get Started with Redpanda Connect using `rpk`](https://docs.redpanda.com/connect/get-started/quickstarts/rpk/) for installation instructions.

### [](#enterprise-license)Enterprise license

The Jira processor requires a Redpanda Enterprise Edition license. See [Enterprise Licensing](https://docs.redpanda.com/connect/get-started/licensing/) for instructions on generating a trial license and setting the `REDPANDA_LICENSE` environment variable.

### [](#jira-environment-variables)Jira environment variables

The examples in this cookbook use environment variables for Jira credentials and configuration. This allows you to keep sensitive credentials secure and separate from your pipeline configuration files.

1.  [Generate a Jira API token](https://id.atlassian.com/manage-profile/security/api-tokens).

2.  Set up your environment variables:

    ```bash
    JIRA_BASE_URL=https://<domain>.atlassian.net (1)
    JIRA_USERNAME=<email-address> (2)
    JIRA_API_TOKEN=<api-token> (3)
    JIRA_PROJECT=<project-key> (4)
    ```

    | 1 | Your Jira instance URL. You can find this in your browser when logged into Jira. |
    | --- | --- |
    | 2 | Your Jira account email address. |
    | 3 | The API token generated from your Atlassian account. |
    | 4 | The key of the Jira project you want to query (such as DOC or K8S). |


You can export these variables in your shell or place them in a `.env` file for use with `rpk connect run --env-file <env-file>`.

## [](#use-jira-as-an-input)Use Jira as an input

To use Jira as an input, combine the `generate` input with the Jira processor. This pattern triggers Jira queries at regular intervals or on-demand.

### [](#query-jira-periodically)Query Jira periodically

This example queries Jira every 30 seconds for recent issues:

```yaml
input:
  generate:
    mapping: |
      root.jql = "project = ${JIRA_PROJECT} AND created >= -1d ORDER BY created DESC"
      root.maxResults = 50
      root.fields = ["key", "summary", "status", "assignee", "created"]
    interval: 30s

pipeline:
  processors:
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"
        max_results_per_page: 50
        timeout: 30s
        backoff:
          max_retries: 10

    # Transform each issue
    - mapping: |
        root.issue_key = this.key
        root.summary = this.fields.summary
        root.status = this.fields.status.name
        root.assignee = if this.fields.assignee != null {
          this.fields.assignee.displayName
        } else { "Unassigned" }
        root.created = this.fields.created

output:
  stdout:
    codec: lines
```

### [](#one-time-query)One-time query

For a single query, use `count` instead of `interval`:

```yaml
input:
  generate:
    mapping: |
      root.jql = "project = ${JIRA_PROJECT} ORDER BY created DESC"
      root.maxResults = 100
      root.fields = ["key", "summary", "status"]
    count: 1  # Run only once

pipeline:
  processors:
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"

output:
  stdout: {}
```

### [](#advanced-query-with-custom-fields)Advanced query with custom fields

```yaml
input:
  generate:
    mapping: |
      root.jql = "assignee = currentUser() AND status IN (Open, 'In Progress') ORDER BY updated DESC"
      root.maxResults = 20
      root.fields = [
        "key",
        "summary",
        "status",
        "assignee",
        "priority",
        "created",
        "updated",
        "customfield_10001"  # Custom field ID
      ]
      root.startAt = 0
    interval: 60s

pipeline:
  processors:
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"
        max_results_per_page: 100

    # Transform each individual issue message
    - mapping: |
        root.key = this.key
        root.summary = this.fields.summary
        root.status = this.fields.status.name
        root.priority = this.fields.priority.name
        root.custom_field = this.fields.customfield_10001

output:
  kafka:
    addresses: ["localhost:9092"]
    topic: jira-issues
```

## [](#use-jira-as-an-output)Use Jira as an output

To use Jira as an output, place the Jira processor before a `drop` output. This executes queries without forwarding results downstream.

### [](#query-and-drop-results)Query and drop results

```yaml
input:
  stdin:
    codec: lines

pipeline:
  processors:
    # Prepare Jira query from incoming message
    - mapping: |
        root.jql = this.jql.or("project = ${JIRA_PROJECT} AND status = Open")
        root.maxResults = this.maxResults.or(100)
        root.fields = this.fields.or(["key", "summary", "status"])

    # Execute Jira query
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"
        backoff:
          max_retries: 3
        timeout: 30s

    # Log each issue before dropping
    - log:
        message: "Processing Jira issue: ${! this.key } - ${! this.fields.summary }"
        level: INFO

output:
  drop: {}
```

### [](#trigger-queries-from-kafka)Trigger queries from Kafka

```yaml
input:
  kafka:
    addresses: ["localhost:9092"]
    topics: ["jira-query-requests"]
    consumer_group: jira-query-consumer

pipeline:
  processors:
    # Parse incoming query request
    - mapping: |
        root.jql = this.query
        root.maxResults = 50

    # Execute query
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"

    # Process each individual issue
    - mapping: |
        root.query_id = uuid_v4()
        root.timestamp = now()
        root.issue_key = this.key
        root.issue_summary = this.fields.summary

output:
  drop: {}
```

## [](#input-message-format)Input message format

The Jira processor expects input messages containing valid Jira queries in JSON format:

```json
{
  "jql": "project = MYPROJECT AND status = Open",
  "maxResults": 50,
  "fields": ["key", "summary", "status", "assignee"]
}
```

### [](#required-fields)Required fields

-   `jql`: The JQL (Jira Query Language) query string


### [](#optional-fields)Optional fields

-   `maxResults`: Maximum number of results to return (default: 50)

-   `fields`: Array of field names to include in the response


## [](#configuration-options)Configuration options

For a complete list of configuration options, see [Jira Processor Reference](https://docs.redpanda.com/connect/components/processors/jira/).

## [](#jql-query-language)JQL query language

The Jira processor supports querying Jira issues using JQL (Jira Query Language). JQL is a flexible, text-based query language similar to SQL that allows you to search for issues matching specific criteria.

### [](#common-jql-patterns)Common JQL patterns

Here are common JQL patterns for filtering issues:

#### [](#recent-issues-by-project)Recent issues by project

```jql
project = <YOUR_PROJECT> AND created >= -7d ORDER BY created DESC
```

#### [](#issues-assigned-to-current-user)Issues assigned to current user

```jql
assignee = currentUser() AND status != Done
```

#### [](#issues-by-status)Issues by status

```jql
project = <YOUR_PROJECT> AND status IN (Open, 'In Progress', 'To Do')
```

#### [](#issues-by-priority)Issues by priority

```jql
project = <YOUR_PROJECT> AND priority = High ORDER BY created DESC
```

#### [](#issues-updated-recently)Issues updated recently

```jql
project = <YOUR_PROJECT> AND updated >= -1d ORDER BY updated DESC
```

## [](#run-the-examples)Run the examples

Each example in this cookbook can be saved as a YAML file and run individually. The examples use environment variables for credentials and configuration, so you don’t need to modify the YAML files.

1.  Set up your environment:

    ```bash
    # Set Jira credentials
    export JIRA_BASE_URL=https://your-domain.atlassian.net
    export JIRA_USERNAME=your-email@example.com
    export JIRA_API_TOKEN=your-api-token
    export JIRA_PROJECT=YOUR_PROJECT
    ```

2.  Save any example to a YAML file (for example, `jira-query.yaml`).

3.  Run the example:

    ```bash
    rpk connect run jira-query.yaml
    ```


## [](#output-message-format)Output message format

The Jira processor returns **individual issue messages**, rather than a response object with an `issues` array.

Each message output by the Jira processor represents a single issue:

```json
{
  "id": "12345",
  "key": "DOC-123",
  "fields": {
    "summary": "Example issue",
    "status": {
      "name": "In Progress"
    },
    "assignee": {
      "displayName": "John Doe"
    },
    "created": "2025-01-15T10:30:00.000-0800",
    "updated": "2025-01-16T14:20:00.000-0800"
  }
}
```

## [](#process-output-messages)Process output messages

When processing messages from the Jira processor, keep in mind:

-   **Do not** use `unarchive` to split an `issues` array

-   **Do not** reference `this.issues` or `this.total` in your mappings

-   **Do** access fields directly such as `this.key`, `this.fields.summary`.


### [](#handle-null-fields)Handle null fields

Some Jira fields can be null and require conditional handling:

```yaml
pipeline:
  processors:
    - mapping: |
        # Assignee can be null
        root.assignee = if this.fields.assignee != null {
          this.fields.assignee.displayName
        } else { "Unassigned" }

        # Reporter can be null
        root.reporter = if this.fields.reporter != null {
          this.fields.reporter.displayName
        } else { "Unknown" }

        # Resolution date is null for open issues
        root.resolved_at = this.fields.resolutiondate

        # Calculate lead time only for resolved issues
        root.lead_time_days = if this.fields.resolutiondate != null {
          (this.fields.resolutiondate.ts_parse("2006-01-02T15:04:05.999-0700").ts_unix() -
           this.fields.created.ts_parse("2006-01-02T15:04:05.999-0700").ts_unix()) / 86400
        } else { null }
```

The Jira processor automatically handles pagination internally using the `max_results_per_page` setting. The processor:

1.  Makes the initial request with `startAt=0`.

2.  Checks if more results are available.

3.  Automatically fetches subsequent pages until all results are retrieved.

4.  Outputs each issue as an individual message.


You don’t need to handle pagination manually. The processor streams all matching issues as separate messages.

## [](#error-handling)Error handling

The Jira processor includes built-in retry logic for rate limiting (HTTP 429) and configurable timeouts. See [Jira Processor Reference](https://docs.redpanda.com/connect/components/processors/jira/) for detailed configuration options.

## [](#create-and-update-jira-issues)Create and update Jira issues

The Jira processor is **read-only** and only supports querying. To create or update Jira issues, you must use the `http` processor with the Jira REST API.

### [](#create-a-jira-issue)Create a Jira issue

Use the HTTP processor to POST to the Jira REST API `/issue` endpoint. This example creates a Task issue with a title and description:

```yaml
# Create Jira Issue with HTTP Processor
#
# The Jira processor is read-only and cannot create or update issues.
# Use the HTTP processor to create Jira issues using the REST API.

input:
  generate:
    mapping: |
      root = {
        "title": "Example issue from Redpanda Connect",
        "description": "This issue was created using the HTTP processor",
        "issue_type": "Task"
      }
    count: 1

pipeline:
  processors:
    - mapping: |
        root = {
          "fields": {
            "project": {
              "key": "${JIRA_PROJECT}"
            },
            "summary": this.title,
            "description": {
              "type": "doc",
              "version": 1,
              "content": [
                {
                  "type": "paragraph",
                  "content": [
                    {
                      "type": "text",
                      "text": this.description
                    }
                  ]
                }
              ]
            },
            "issuetype": {
              "name": this.issue_type
            }
          }
        }

    - label: create_issue
      http:
        url: "${JIRA_BASE_URL}/rest/api/3/issue"
        verb: POST
        headers:
          Content-Type: application/json
          Accept: application/json
        basic_auth:
          enabled: true
          username: "${JIRA_USERNAME}"
          password: "${JIRA_API_TOKEN}"

    - mapping: |
        root.issue_key = this.key
        root.issue_id = this.id
        root.self_link = this.self

output:
  stdout:
    codec: lines

tests:
  - name: Transform data for creating issue
    target_processors: '/pipeline/processors/0'
    environment:
      JIRA_PROJECT: "TEST"
    input_batch:
      - json_content:
          title: "Test issue"
          description: "Test description"
          issue_type: "Bug"
    output_batches:
      - - json_equals:
            fields:
              project:
                key: "TEST"
              summary: "Test issue"
              description:
                type: "doc"
                version: 1
                content:
                  - type: "paragraph"
                    content:
                      - type: "text"
                        text: "Test description"
              issuetype:
                name: "Bug"

  - name: Mock create issue API response
    target_processors: '/pipeline/processors'
    mocks:
      create_issue:
        mapping: |
          root.id = "10001"
          root.key = "DOC-789"
          root.self = "https://your-domain.atlassian.net/rest/api/3/issue/10001"
    input_batch:
      - json_content:
          title: "Test issue"
          description: "Test description"
          issue_type: "Task"
    output_batches:
      - - json_equals:
            issue_key: "DOC-789"
            issue_id: "10001"
            self_link: "https://your-domain.atlassian.net/rest/api/3/issue/10001"
```

-   **Atlassian Document Format (ADF)**: The `description` field uses ADF, Jira’s rich text format. For simple text, wrap it in a paragraph structure as shown.

-   **Issue type**: Common types are `Task`, `Bug`, `Story`, `Epic`. The type must exist in your Jira project.

-   **Required fields**: At minimum, you need `project`, `summary`, and `issuetype`. Other fields may be required based on your Jira configuration.

-   **Response**: The API returns the created issue’s `key`, `id`, and `self` (URL).


## [](#troubleshoot)Troubleshoot

### [](#license-errors)License errors

If you receive license-related errors:

1.  Verify the license is exported:

    ```bash
    echo $REDPANDA_LICENSE
    ```

2.  Regenerate if needed:

    ```bash
    rpk generate license --name "<first-name>" \
      --last-name "<last-name>" \
      --email "<email>" \
      --company "<company>"
    export REDPANDA_LICENSE=$(cat ./redpanda.license)
    ```

3.  Check license expiration:

    The trial license expires after 30 days. Check the output from `rpk generate license` for the expiration date.


### [](#authentication-errors)Authentication errors

If you receive 401 Unauthorized errors:

-   Verify your API token is correct

-   Ensure you’re using your email address as the username

-   Check that your Jira instance URL is correct (include `https://`)


### [](#rate-limiting)Rate limiting

If you hit Jira rate limits (HTTP 429):

-   Increase `max_retries` in the Jira processor config

-   Reduce query frequency by increasing `interval`

-   Use more specific JQL queries to reduce result sizes


### [](#query-performance)Query performance

For large result sets:

-   Use more specific JQL queries

-   Limit fields with the `fields` parameter

-   Reduce `max_results_per_page` if memory is constrained


## [](#see-also)See also

-   [Jira Processor Reference](https://docs.redpanda.com/connect/components/processors/jira/)

-   [Jira REST API Documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/)

-   [JQL Query Guide](https://www.atlassian.com/software/jira/guides/jql)

-   [Bloblang Interpolation](https://docs.redpanda.com/connect/configuration/interpolation/)


## Suggested labs

-   [Stream Jira Issues to Redpanda for Real-Time Metrics](https://docs.redpanda.com/labs/docker-compose/jira-metrics-pipeline/)

[Search all labs](https://docs.redpanda.com/labs)