How to export Jira worklogs to CSV or Excel: issue totals vs worklog rows

Jira's built-in CSV export rolls worklogs up to one Time Spent number per issue, and its Log Work columns carry wrong dates. Three ways to get real worklog rows.

Jira exports worklogs to CSV in two shapes, and most of the pain in this topic comes from getting the shape you didn’t want. The two-click route, the Export menu on an issue search, produces one row per issue with Time Spent as a single rolled-up number of seconds. That answers budget questions and nothing else: who logged the hours and on which days is not in the file in any usable form. To get actual worklog rows out of Jira you have three options. The Log Work columns hidden in the all-fields export, which carry a date bug serious enough to disqualify them for billing. A short script against the worklog REST API. Or an app that keeps its own copy of the worklog data and exports it cleanly.

I co-founded Planim Time, a desktop Jira tracker, and the Export CSV button in its report view exists because I got tired of running the script version by hand every month. This post walks each route, what the file actually contains, and the two open Jira bugs plus one endpoint shutdown to know about before trusting any of them.

Decide first: totals or rows

Every worklog export question reduces to grain. Per-issue totals tell you whether a project is over budget. Per-entry rows are what an invoice, a timesheet, or a payroll reconciliation needs: one line per act of logged work, with an author and a date. Jira’s built-in export produces the first natively and the second only in a broken form, so pick your route by the question you’re answering.

What you’re producingGrainRoute
Budget check on a project or versionTotalsIssue search export
Client invoice with datesRowsAPI script, or an app
Per-person timesheet for a monthRowsAPI script, or an app
Quick look at where time wentTotalsIssue search export

If the totals grain is enough, you may not need an export at all; the built-in time tracking reports cover some of those questions in place.

Route 1: the issue search export

Run a search in the issue navigator, usually narrowed with the worklog JQL fields:

worklogAuthor = currentUser() AND worklogDate >= "2026-06-01" AND worklogDate <= "2026-06-30"

Then open the Export menu and pick a CSV variant: current fields exports the columns you see, all fields exports everything. Two facts about what lands in the file.

First, the Time Spent column is one aggregate number per issue, in raw seconds, summed over every worklog the issue has ever had. Your June JQL narrowed which issues qualify, but the total still includes January’s hours. There is no way to make this column respect a date range, because the JQL worklog fields filter issues, not entries.

Second, the all-fields export does contain per-entry data, in a set of Log Work columns: one column per worklog, each cell packing the comment, a timestamp, the author, and the seconds into one semicolon-separated string. It looks parseable, and people build invoicing spreadsheets on top of it. Don’t. The timestamp in that cell is when the entry was created, not the started date the user entered. Atlassian tracks this as JRACLOUD-77926 and JRASERVER-64025, both open for years. Anyone who backfills worklogs after the fact, which in my experience is most people in the week before an invoice goes out, gets every backfilled entry stamped with the day they typed it in rather than the day they worked. The file looks right, sums right, and bills the wrong days.

The columns also multiply: one column per worklog means an issue with eighty entries adds eighty columns to the sheet, and the widest issue in your search sets the width for everyone. And the export itself is capped at 10,000 issues per file on Cloud (raised from 1,000 in March 2025); past that you batch the JQL or move to the API.

Verdict: fine for totals, structurally unfit for timesheets.

Route 2: a script against the worklog API

The REST API is the only plugin-free way to real worklog rows, and the data it returns is correct: the started field is the date the user entered, exactly what the CSV export loses.

One 2025 change first. Atlassian shut down the old /rest/api/3/search endpoint in late 2025, so most pre-2025 export scripts you’ll find on Stack Overflow now fail with 410 Gone. The replacement is GET /rest/api/3/search/jql, which paginates with a nextPageToken instead of startAt and returns only IDs unless you ask for fields explicitly. Server and Data Center keep the old /rest/api/2/search.

The whole job is two loops: find the issues, then pull and filter their worklogs.

import csv, requests

BASE = "https://your-domain.atlassian.net/rest/api/3"
AUTH = ("[email protected]", JIRA_TOKEN)
SINCE, UNTIL = "2026-06-01", "2026-06-30"

def issue_keys():
    token, keys = None, []
    while True:
        params = {"jql": f'worklogDate >= "{SINCE}" AND worklogDate <= "{UNTIL}"',
                  "fields": "key", "maxResults": 100}
        if token:
            params["nextPageToken"] = token
        page = requests.get(f"{BASE}/search/jql", params=params, auth=AUTH).json()
        keys += [i["key"] for i in page["issues"]]
        token = page.get("nextPageToken")
        if not token:
            return keys

def rows(key):
    start = 0
    while True:
        page = requests.get(f"{BASE}/issue/{key}/worklog",
                            params={"startAt": start, "maxResults": 100},
                            auth=AUTH).json()
        for w in page["worklogs"]:
            day = w["started"][:10]          # the date the logger entered
            if SINCE <= day <= UNTIL:
                yield [key, w["author"]["displayName"], day,
                       w["timeSpentSeconds"]]
        start += page["maxResults"]
        if start >= page["total"]:
            return

with open("worklogs.csv", "w", newline="") as f:
    out = csv.writer(f)
    out.writerow(["issue", "author", "date", "seconds"])
    for key in issue_keys():
        out.writerows(rows(key))

The per-entry filter in rows() is not optional. JQL matched issues with at least one June worklog, but those issues arrive with their whole history attached, and without the SINCE <= day <= UNTIL check your June file quietly includes May.

One subtlety we hit building the same logic into Planim Time: started comes back with the timezone offset it was logged in, like 2026-06-30T23:30:00.000+0200. Slicing the first ten characters keeps the date in the logger’s own timezone, which is almost always what an invoice wants. But if your team spans timezones and you normalise everything to UTC instead, entries logged late in the evening migrate across midnight onto the next day, and a month’s total shifts by a few hours at each end. Pick one convention and write it down before someone reconciles the export against payroll.

I left worklog comments out of the CSV on purpose: on Cloud they arrive as Atlassian Document Format, a nested JSON structure, and flattening it to text is its own chore. Auth setup, the 400s and 429s you’ll meet, and the bulk worklog/updated endpoints for whole-instance pulls are all in the worklog REST API guide; the script above is just the export-shaped corner of that API.

Route 3: a Marketplace app

Tempo and Clockwork both maintain a real timesheet surface inside Jira, and both export it to CSV or Excel from their report views, with correct work dates, because they read worklogs through the API rather than through Jira’s export pipeline. If your team already pays for one, its export button is the shortest path in this post and you can stop reading.

If it doesn’t, installing a Marketplace app only to get exports is a hard sell: they need an admin, and they bill per Jira user whether or not that user tracks time. The arithmetic of that, and what teams use instead when the goal is billing clients out of Jira data, is its own topic.

Route 4: a tracker with its own worklog store

Desktop trackers sit in the same position as Marketplace apps, one step closer to the data: they already mirror worklogs through the API into a local store, so exporting is just writing that store to a file. In Planim Time the report view has an Export CSV button with selectable columns, and the dates are the started dates because the store is API-fed; it is, honestly, the script from Route 2 productised, with the sync loop keeping the data current instead of a monthly manual run.

This route makes sense when the export is recurring. If finance wants a worklog file every month, a tracker that’s already syncing beats re-running a script and re-explaining its timezone convention.

Opening the file in Excel

Three small things go wrong between a correct CSV and a correct spreadsheet.

Durations arrive in seconds, both in the native export’s Time Spent column and in the script above. Convert with =D2/3600 for decimal hours, or =D2/86400 with the cell formatted as [h]:mm for a duration display. Resist exporting pre-formatted strings like 1h 30m; they read nicely and sum as zero.

The native all-fields export’s Log Work cells contain semicolons inside quoted fields. Excel locales that use semicolon as the list separator (most of Europe) split those cells into fragments on a plain double-click open. Import via Data, From Text/CSV, and set the delimiter to comma explicitly.

Keep dates as ISO 2026-06-09 strings. Excel’s locale guessing turns ambiguous formats like 06/09/2026 into June or September depending on the machine that opens the file, and a billing dispute over which is not hypothetical.

Which route, in one table

ScenarioRoute
One-off budget checkIssue search export, current fields
Invoice or timesheet, occasionallyThe API script above
Invoice or timesheet, every monthAn app or tracker with its own export
Whole-instance reporting pipelineAPI, the bulk endpoints from the REST guide

The built-in export isn’t broken, it’s answering a different question: how much time each issue has absorbed, ever. The moment your CSV needs a true date on every row, the native pipeline has nothing safe to offer, and the API stops being the advanced option and becomes the only one.

Frequently asked questions

How do I export worklogs from Jira to Excel?
It depends on the grain you need. For total Time Spent per issue, run a JQL search, open the Export menu, and download the CSV; Excel opens it directly. For individual worklog entries with author and date, the built-in export won't do: pull them through the worklog REST API with a short script, or use a time tracking app that exports its own timesheet. The all-fields export does include Log Work columns, but they carry the entry's creation timestamp instead of the work date, which makes them unsafe for timesheets.
Can I export Jira worklogs without a Marketplace plugin?
Yes, two ways. The issue search export needs nothing installed and gives you per-issue Time Spent totals. For per-entry rows, a script against the worklog REST API needs only your own API token, no admin and no plugin. Between the two there is no native button that produces a clean worklog-per-row file.
Why does my Jira CSV export show the wrong worklog dates?
It's a long-standing Jira bug, tracked as JRACLOUD-77926 and JRASERVER-64025 and still open. The Log Work columns in the all-fields CSV export carry the timestamp when the worklog entry was created, not the started date the user entered. Any worklog logged after the fact lands on the wrong day in the export. The REST API returns the real started field, so scripts and API-backed tools are not affected.
How many issues can I export from Jira at once?
Jira Cloud's issue search export is capped at 10,000 issues per file, raised from 1,000 in March 2025. Beyond that, split the JQL into smaller batches (by date range or project) and export each one, or read the data through the REST API, which pages without that cap.
How do I export Jira worklogs for a specific date range?
JQL can only narrow the issues: worklogDate >= "2026-06-01" AND worklogDate <= "2026-06-30" finds every issue with at least one worklog in June, but the export still includes those issues' entire history. To cut the entries themselves to a range, filter each worklog's started field in a script, or pass startedAfter and startedBefore to the per-issue worklog API endpoint.