Introduction
Power BI and Jira are two of the most widely used tools in modern software organizations, one for data visualization and the other for project management. Getting them to talk to each other sounds straightforward, but in practice it's a surprisingly complex problem. As the developer behind Pallas Apps' Power BI Connector for Jira, I've spent a significant amount of time solving exactly this problem, and in this article I want to share what that process looks like. This is intentionally high-level — there's a lot more depth to each of these areas than what's covered here.
Why OData
Using a database connector was an option, but that would require storing Jira data externally, which I wanted to avoid for data privacy reasons. Processing everything in memory and serving it on demand made OData the most viable path. It's a standardized protocol that Power BI understands natively.
Unfortunately, Jira's REST API returns plain JSON, and since OData is a protocol rather than a data format, you can't simply convert that JSON into something OData-compliant. You have to rebuild the data from scratch, mapping each Jira field to its corresponding OData type and serving it through an OData-conformant service.
The Metadata Document
Before Power BI will accept any data from your OData service, it first requests a metadata document, sometimes called the service metadata document or EDMX document. This document lists every field that will be imported along with its OData type. Power BI uses this to understand the shape of the data before it receives it, so if your metadata document declares a field as Edm.String and your service then sends a numeric value, Power BI will error out.
Here is a simple example of what a metadata document looks like:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="JiraService" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="Issue">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.String" Nullable="false" />
<Property Name="Key" Type="Edm.String" />
<Property Name="Summary" Type="Edm.String" />
</EntityType>
<EntityContainer Name="JiraContainer">
<EntitySet Name="Issues" EntityType="JiraService.Issue" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Mapping Jira Fields to OData Types
This is one of the more difficult parts of building the connector. Jira has a /rest/api/3/fields endpoint that returns metadata about every field available in a given instance, including a schema object that describes the field's type. This is a useful starting point, but it doesn't get you all the way there.
For straightforward fields the mapping is simple. Take the Created field as an example. Here is what the fields endpoint returns for it:
{
"id": "created",
"key": "created",
"name": "Created",
"custom": false,
"orderable": false,
"navigable": true,
"searchable": true,
"clauseNames": ["created", "createdDate"],
"schema": {
"type": "datetime",
"system": "created"
}
}
The datetime schema type above is accurate as the REST API will return the following format for it:
"created": "2026-03-20T19:34:00.293-0400"
The value includes the date, time, and a timezone offset, which maps cleanly to the OData type Edm.DateTimeOffset. Other fields are considerably more complex. Worklogs are a good example. The fields endpoint describes it as an array type:
{
"id": "worklog",
"key": "worklog",
"name": "Log Work",
"custom": false,
"orderable": true,
"navigable": false,
"searchable": true,
"clauseNames": [],
"schema": {
"type": "array",
"items": "worklog",
"system": "worklog"
}
}
And an example of the worklog data returned for an issue:
"worklog": {
"startAt": 0,
"maxResults": 20,
"total": 1,
"worklogs": [
{
"author": {
"accountId": "5fb405a7cbead51283dd9b63",
"displayName": "Jack Dillon",
"emailAddress": "example@email.com",
"timeZone": "America/New_York"
},
"comment": "",
"created": "2026-05-18T16:41:06.379-0400",
"updated": "2026-05-18T16:41:06.379-0400",
"started": "2026-05-18T08:41:01.759-0400",
"timeSpent": "1d",
"timeSpentSeconds": 28800,
"id": "10001",
"issueId": "22502"
},
...
]
}
Technically the worklogs value is an array, but its within the "worklog" field and it has nested data with different data types. For fields like this I made a deliberate decision to flatten the data and retain only the values that are actually useful for reporting purposes.
The approach I took for mapping was to go through every possible Jira field type one by one and define how it should be handled. There's no shortcut, as each type has its own structure and its own edge cases.
Multi-Value Fields and Separate Tables
Some Jira fields store multiple values per issue. Sprints, linked issues, and comments are common examples. This breaks the standard one-value-per-row structure that Power BI expects, so you can't just flatten these into a single column.
The solution I chose was to turn these fields into their own separate tables that link back to the main issue table via the issue key. This is the same pattern you'd use in a relational database and Power BI handles it well. You can define relationships between the tables and use them in your visualizations just like any other related data.
Custom Fields
Custom fields introduce another layer of complexity. Jira's API returns custom field values using the field ID rather than the field name, so instead of a column called "My Custom Field" you'll see customfield_12345. The /rest/api/3/fields endpoint provides the mapping between IDs and human-readable names, and you'll need to apply that mapping before serving the data to Power BI.
You'll also run into issues with field names that contain characters Power BI doesn't allow in column names. The # character is a common example. If a custom field name contains it, Power BI will throw an error. The fix is straightforward: replace any invalid characters with an allowed alternative. These kinds of gotchas add up, and since Jira and Power BI were never designed to work together there's no exhaustive list of them. You find them by testing against real Jira instances with real data.
Authentication
The user enters their Jira API token directly into Power BI's OData connector modal. Power BI passes the credentials as a HTTP header to our backend on every request, which uses it to authenticate with Jira's API and fetch the data on the user's behalf.
The Backend and Frontend
Atlassian's Forge platform, where the Jira app runs, does not support accepting arbitrary HTTP requests from external services like Power BI. This meant building a separate backend service capable of receiving requests at any time, authenticating the user, fetching data from Jira, transforming it into OData-compliant format, and serving it back to Power BI.
Once the backend was in place, building the Forge frontend that users interact with inside Jira was comparatively straightforward. The goal was to make it as simple as possible. Users can either write their own JQL query or use basic filters such as project, issue type, and reporter to define which issues they want included. The UI was designed with Jira in mind, so users can filter data as they normally would, without having to learn a new tool. A step-by-step walkthrough of creating a connector is available in our documentation.
Should You Build This Yourself?
If you're an experienced developer working with a well-defined, finite set of Jira fields that won't change much over time, building your own connector could be worthwhile. You'll have full control over exactly what data gets pulled and how it's structured.
However, if your team is pulling in different data sets across multiple projects and using a large number of custom fields the scope grows enormously. A production-ready connector needs to handle every possible Jira field, every possible field name, and the full range of edge cases that come with supporting diverse Jira configurations. With how often Jira changes, I've found it to be a significant ongoing maintenance commitment, not a one-time build.
For most teams, the better investment is using an existing connector that abstracts all the data engineering complexity. Our connector has been on the Atlassian Marketplace for almost a year and I maintain it full time. If you have questions about anything in this article or want to learn more, feel free to reach out at jack@pallas-apps.com.