> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Capabilities

JQL support for issues

The issue kind selector supports two complementary filtering mechanisms:

  • jql (Jira Query Language) : Runs server-side on the Jira API. It controls which issues are fetched from Jira in the first place. Use this for broad filtering to reduce the volume of data returned by the API (e.g. excluding done issues, limiting to specific projects).
  • query (JQ expression) : Runs client-side on each fetched issue. It evaluates a JQ boolean expression against the raw issue data and skips any issue where the expression returns false. Use this for fine-grained filtering on fields available in the API response (e.g. filtering by a custom field value or sprint name).

Both can be used together in the same selector. When combined, jql narrows the results from Jira, and query further filters them before ingestion into Port.

resources:
- kind: issue # JQL filtering can only be used with the "issue" kind
selector:
query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped.
jql: "status != Done" # JQL query, will only ingest issues whose status is not "Done"
port:

Fields support for the issue kind

The Jira integration allows you to customize what fields are available for ingestion using the fields selector. The fields selector accepts a comma-separated string containing what fields are available. Possible values are *all, *navigate, <field_name> and -<field_name> (specifically for excluding fields).

The default value is set to *all which makes all fields available by default.

More details what values the field selector allows is available on Jira's issue API documentation.

Ingest sprint field for the issue kind

By default, the Jira integration does not include information on the issues' sprint. To ingest sprint information, a custom field must be added to issues to display sprints. This custom field is then included in the Jira issues mapping configuration.

Follow the steps below:

  1. Add Sprint as a custom field to Jira issue on your Jira dashboard. Take note of the custom field ID for the sprints field. This ID can be gotten by calling the fields API to first retreive the list of fields for issues which returns a payload like so:

    Issues field API response (click to expand)
    [
    {
    "id": "thumbnail",
    "key": "thumbnail",
    "name": "Images",
    "custom": false,
    "orderable": false,
    "navigable": true,
    "searchable": false,
    "clauseNames": []
    },
    {
    "id": "created",
    "key": "created",
    "name": "Created",
    "custom": false,
    "orderable": false,
    "navigable": true,
    "searchable": true,
    "clauseNames": [
    "created",
    "createdDate"
    ],
    "schema": {
    "type": "datetime",
    "system": "created"
    }
    },
    {
    "id": "customfield_11111",
    "key": "customfield_11111",
    "name": "Sprint",
    "untranslatedName": "Sprint",
    "custom": true,
    "orderable": true,
    "navigable": true,
    "searchable": true,
    "clauseNames": [
    "cf[11111]",
    "Sprint"
    ],
    "schema": {
    "type": "array",
    "items": "json",
    "custom": "com.pyxis.greenhopper.jira:gh-sprint",
    "customId": 11111
    }
    }
    ]

    Locate the Sprint field and take note of the ID - in this case, customfield_11111.

  2. Extend the default blueprint to include a field for sprint:

    Issue blueprint with sprint (click to expand)
    {
    "identifier": "jiraIssue",
    "title": "Jira Issue",
    "icon": "Jira",
    "schema": {
    "properties": {
    "url": {
    "title": "Issue URL",
    "type": "string",
    "format": "url",
    "description": "URL to the issue in Jira"
    },
    "status": {
    "title": "Status",
    "type": "string",
    "description": "The status of the issue"
    },
    "issueType": {
    "title": "Type",
    "type": "string",
    "description": "The type of the issue"
    },
    "components": {
    "title": "Components",
    "type": "array",
    "description": "The components related to this issue"
    },
    "assignee": {
    "title": "Assignee",
    "type": "string",
    "format": "user",
    "description": "The user assigned to the issue"
    },
    "reporter": {
    "title": "Reporter",
    "type": "string",
    "description": "The user that reported to the issue",
    "format": "user"
    },
    "creator": {
    "title": "Creator",
    "type": "string",
    "description": "The user that created to the issue",
    "format": "user"
    },
    "priority": {
    "title": "Priority",
    "type": "string",
    "description": "The priority of the issue"
    },
    "sprint": {
    "title": "Sprint",
    "type": "string",
    "description": "The last sprint this issue belongs to"
    },
    "created": {
    "title": "Created At",
    "type": "string",
    "description": "The created datetime of the issue",
    "format": "date-time"
    },
    "updated": {
    "title": "Updated At",
    "type": "string",
    "description": "The updated datetime of the issue",
    "format": "date-time"
    }
    }
    },
    "calculationProperties": {
    "handlingDuration": {
    "title": "Handling Duration (Days)",
    "icon": "Clock",
    "description": "The amount of time in days from issue creation to issue resolution",
    "calculation": "if (.properties.resolutionDate != null and .properties.created != null) then ((.properties.resolutionDate[0:19] + \"Z\" | fromdateiso8601) - (.properties.created[0:19] + \"Z\" | fromdateiso8601)) / 86400 else null end",
    "type": "number"
    }
    },
    "mirrorProperties": {},
    "aggregationProperties": {},
    "relations": {
    "project": {
    "target": "jiraProject",
    "title": "Project",
    "description": "The Jira project that contains this issue",
    "required": false,
    "many": false
    },
    "parentIssue": {
    "target": "jiraIssue",
    "title": "Parent Issue",
    "required": false,
    "many": false
    },
    "subtasks": {
    "target": "jiraIssue",
    "title": "Subtasks",
    "required": false,
    "many": true
    },
    "assignee": {
    "target": "jiraUser",
    "title": "Assignee",
    "required": false,
    "many": false
    },
    "reporter": {
    "target": "jiraUser",
    "title": "Reporter",
    "required": false,
    "many": false
    }
    }
    }

    Sprints field in issues payload response

    On the issue response returned by calling the Jira issues API, sprints is returned as an array of sprints:

    Sprints field in Jira API response (click to expand)
    {
    . . .,
    "customfield_11111": [
    {
    "id": 37,
    "name": "Sprint 32",
    "state": "closed",
    "boardId": 1,
    "goal": "",
    "startDate": "2024-07-08T11:59:07.316Z",
    "endDate": "2024-07-28T21:00:00.000Z",
    "completeDate": "2024-07-29T14:04:34.397Z"
    },
    {
    "id": 38,
    "name": "Sprint 33",
    "state": "closed",
    "boardId": 1,
    "goal": "",
    "startDate": "2024-07-29T14:05:25.295Z",
    "endDate": "2024-08-18T23:06:20.000Z",
    "completeDate": "2024-08-20T09:19:48.396Z"
    },
    {
    "id": 40,
    "name": "Sprint 34",
    "state": "closed",
    "boardId": 1,
    "goal": "",
    "startDate": "2024-08-20T09:20:27.259Z",
    "endDate": "2024-09-08T20:30:00.000Z",
    "completeDate": "2024-09-10T13:50:01.397Z"
    },
    {
    "id": 42,
    "name": "Sprint 35",
    "state": "active",
    "boardId": 1,
    "goal": "",
    "startDate": "2024-09-10T13:51:44.000Z",
    "endDate": "2024-10-15T01:01:00.000Z"
    }
    ],
    . . .
    }

    For the purpose of this guide, we are simply retrieving the ID of the latest sprint.

  3. Add the mapping configuration for the sprint field:

    Issue with sprint field mapping configuration (click to expand)
    createMissingRelatedEntities: true
    deleteDependentEntities: true
    resources:
    - kind: issue
    selector:
    query: "true"
    jql: "statusCategory != Done"
    port:
    entity:
    mappings:
    identifier: .key
    title: .fields.summary
    blueprint: '"jiraIssue"'
    properties:
    url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key
    status: .fields.status.name
    issueType: .fields.issuetype.name
    components: .fields.components
    assignee: .fields.assignee.emailAddress
    reporter: .fields.reporter.emailAddress
    creator: .fields.creator.emailAddress
    priority: .fields.priority.name
    sprint: .fields.customfield_11111[-1].name // ""
    created: .fields.created
    updated: .fields.updated
    relations:
    project: .fields.project.key
    parentIssue: .fields.parent.key
    subtasks: .fields.subtasks | map(.key)

    Where customfield_11111 is your sprint field.

  4. Click on Resync and watch sprints information being pulled alongside issues data.

If you have enabled the sprint kind, you can link issues directly to their sprint entity in Port. This creates a navigable relation between jiraIssue and jiraSprint, enabling cross-blueprint views and aggregations.

Extend the jiraIssue blueprint to add the sprint relation:

"relations": {
"sprint": {
"target": "jiraSprint",
"title": "Sprint",
"description": "The sprint this issue belongs to",
"required": false,
"many": false
}
}

Then add the relation mapping to your issue configuration, replacing customfield_11111 with your sprint field ID:

relations:
sprint: .fields.customfield_11111[-1].id | tostring

The sprint field returns an array of sprints an issue has belonged to. .[-1] retrieves the most recent sprint. The .id maps to the jiraSprint identifier which is .id | tostring.

Sprint field ID

The sprint field ID (customfield_XXXXX) is instance-specific. Follow step 1 in the section above to retrieve it from your Jira fields API.

Jira issues carry a reference to their parent epic via the epic field in the issue API response. When the epic kind is enabled, you can wire this reference as a relation on jiraIssue pointing to jiraEpic, enabling cross-blueprint views — for example, listing all issues under a given epic directly from the epic entity page in Port.

Extend the jiraIssue blueprint to add the epic relation:

"relations": {
"epic": {
"target": "jiraEpic",
"title": "Epic",
"description": "The epic this issue belongs to",
"required": false,
"many": false
}
}

Then add the relation mapping to your issue configuration:

relations:
epic: (.fields.epic.id // null) | if . != null then tostring else null end

The .fields.epic field is populated by the Jira Software Cloud API for issues that belong to an epic. The mapping safely handles issues with no epic — .fields.epic is null for issues that do not belong to any epic, and the expression returns null in that case rather than failing. The .id is cast to a string to match the jiraEpic identifier format (.id | tostring).

Epic field availability

.fields.epic is returned by the Jira Software Cloud Agile REST API. It may not be present on all Jira project types. If your issues do not have this field populated, the relation will be null for those issues — no error occurs.

Ingest issues based on the current sprint

Ingesting only issues from the current sprint can be done by combining the sprint property with selector magic:

Issue from current sprint (click to expand)
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: issue
selector:
query: .fields.customfield_11111[-1].name == "Sprint 35" # Replace "Sprint 35" with the name of the current sprint
jql: "statusCategory != Done"
port:
entity:
mappings:
identifier: .key
title: .fields.summary
blueprint: '"jiraIssue"'
properties:
url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key
status: .fields.status.name
issueType: .fields.issuetype.name
components: .fields.components
assignee: .fields.assignee.emailAddress
reporter: .fields.reporter.emailAddress
creator: .fields.creator.emailAddress
priority: .fields.priority.name
sprint: .fields.customfield_11111 | sort_by(.id) | .[-1].name // ""
created: .fields.created
updated: .fields.updated
relations:
project: .fields.project.key
parentIssue: .fields.parent.key
subtasks: .fields.subtasks | map(.key)
Issues with blank Sprint values

If the createMissingRelatedEntities is set to true, issues with blank Sprint values and some empty/null properties will be created in Port. To avoid this, set createMissingRelatedEntities to false.

Ingest worklogs per issue

The worklog kind fetches all time tracking entries logged against each issue and syncs them into Port as jiraWorklog entities. Each worklog is enriched with the key of the issue it belongs to, enabling the issue relation on the jiraWorklog blueprint.

The selector's jql field controls which issues are scanned for worklogs. It defaults to updated >= -1w to avoid re-fetching the entire worklog history on every resync.

resources:
- kind: worklog
selector:
query: 'true'
jql: updated >= -1w
port:
entity:
mappings:
identifier: .id
title: .author.displayName + " - " + .started
blueprint: '"jiraWorklog"'
properties:
timeSpent: .timeSpent
timeSpentSeconds: .timeSpentSeconds
started: .started
relations:
issue: .__issueKey
Live events

The worklog kind supports real-time updates. Port listens to worklog_created, worklog_updated, and worklog_deleted webhook events and updates entities immediately without waiting for the next resync.

The backlog kind fetches issues from each board's backlog and automatically enriches them with the board they belong to. This allows you to see which board an issue sits on directly from the jiraIssue entity in Port.

To use this, add a board relation to your jiraIssue blueprint targeting jiraBoard, then include the backlog kind in your mapping:

resources:
- kind: backlog
selector:
query: 'true'
jql: statusCategory != Done
port:
entity:
mappings:
identifier: .key
title: .fields.summary
blueprint: '"jiraIssue"'
properties:
url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key
status: .fields.status.name
issueType: .fields.issuetype.name
relations:
project: .fields.project.key
board: '.__boardId | tostring'
Kanban boards

Boards that do not have a backlog enabled (such as Kanban boards with backlog disabled) are automatically skipped. This will not cause the resync to fail.

Sync components per project

The component kind imports components from all Jira projects that are accessible to the integration and creates jiraComponent entities in Port.

Each component is automatically associated with the Jira project it belongs to, making it easy to navigate between projects and their components in Port. Component leads are also linked to their corresponding jiraUser entities when available.

Resync only

Jira does not provide webhook events for component changes. As a result, component updates are discovered and synced during the next scheduled resync.

resources:
- kind: component
selector:
query: 'true'
port:
entity:
mappings:
identifier: .id | tostring
title: .name
blueprint: '"jiraComponent"'
properties:
description: .description
assigneeType: .assigneeType
issueCount: .issueCount
ari: .componentBean.ari // null
relations:
project: .__project.key
lead: .lead.accountId // null