Export Jira issues to Google Sheets past the 1,000-row CSV cap, with JQL, the custom field IDs nobody can read, and the search endpoint Atlassian replaced.
Jira exports issues to CSV in about two minutes, and for a small one-off that is the right answer. The reason people end up looking for something else is the 1,000-issue cap on that export, and the fact that it never updates. This guide covers the CSV route and where it stops, the free REST API method that handles any issue count with JQL, and the two details that break most first attempts: unreadable custom field IDs and the search endpoint Atlassian recently replaced.
The two-minute option: CSV export
In Jira, open Filters → View all issues, build the filter you want (JQL works here), then use the export menu and choose Export Excel CSV. Import the file into Sheets via File → Import.
Two limits arrive quickly. The export is capped at 1,000 issues, which a single active project passes in a quarter, and the "all fields" variant produces a very wide sheet with every custom field your instance has ever defined. Pick "current fields" and configure the columns you want in the navigator first.
Jira to Google Sheets for free (Apps Script + REST API)
Create an API token at id.atlassian.com → Security → API tokens. Jira Cloud authenticates with your account email and that token, combined as basic auth. Your site URL is the your-domain.atlassian.net host.
Two things to know before the code. First, Atlassian replaced /rest/api/3/search with /rest/api/3/search/jql on Jira Cloud. The new endpoint pages with an opaque nextPageToken rather than startAt offsets, and it deliberately does not return a total count, so any code that loops until startAt > total needs rewriting. Second, always pass an explicit fields list. Requesting everything on a large result set is dramatically slower and can time out.
function importJiraIssues() { const SITE = "your-domain.atlassian.net"; const EMAIL = "[email protected]"; const TOKEN = "YOUR_API_TOKEN"; const JQL = 'project = ENG AND updated >= -30d ORDER BY created DESC'; const auth = "Basic " + Utilities.base64Encode(EMAIL + ":" + TOKEN); const headers = { Authorization: auth, Accept: "application/json" }; // Custom fields come back as customfield_NNNNN. Fetch the mapping so the // header row says "Story Points" rather than "customfield_10016". const fieldNames = {}; JSON.parse( UrlFetchApp.fetch("https://" + SITE + "/rest/api/3/field", { headers }) .getContentText() ).forEach((f) => { fieldNames[f.id] = f.name; }); const WANTED = ["summary", "status", "assignee", "priority", "created", "resolutiondate", "customfield_10016"]; const rows = []; let nextPageToken = null; do { let url = "https://" + SITE + "/rest/api/3/search/jql" + "?jql=" + encodeURIComponent(JQL) + "&maxResults=100" + "&fields=" + WANTED.join(","); if (nextPageToken) url += "&nextPageToken=" + nextPageToken; const page = JSON.parse( UrlFetchApp.fetch(url, { headers }).getContentText() ); (page.issues || []).forEach((issue) => { const f = issue.fields; rows.push([ issue.key, f.summary || "", f.status ? f.status.name : "", f.assignee ? f.assignee.displayName : "Unassigned", f.priority ? f.priority.name : "", f.created || "", f.resolutiondate || "", f.customfield_10016 || "", ]); }); // No "total" is returned. Stop when Jira stops handing back a token. nextPageToken = page.nextPageToken || null; } while (nextPageToken); const header = ["Key", ...WANTED.map((id) => fieldNames[id] || id)]; const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("Jira") || ss.insertSheet("Jira"); sheet.clearContents(); sheet.getRange(1, 1, 1, header.length).setValues([header]); if (rows.length) { sheet.getRange(2, 1, rows.length, header.length).setValues(rows); }}Run once to authorise, then add a time-driven trigger under Triggers → Add Trigger → Time-driven. Note that nearly every interesting Jira field is an object rather than a string: status, assignee, priority and resolution all need a property picked off them, which is why the code above reads .name and .displayName rather than writing the field directly.
Limits: Apps Script stops at six minutes, so very large JQL results need splitting by date range across runs. Custom field IDs differ per Jira site, so this script is not portable between instances without re-checking them. There is no write-back unless you build it.
Jira Server and Data Center
Self-hosted instances differ in two ways. The path is /rest/api/2/ rather than /rest/api/3/, and authentication uses a personal access token sent as Authorization: Bearer <token>, created from your profile under Personal Access Tokens. Older versions still accept basic auth with a password. The classic startAt and maxResults pagination remains in place on these versions, so the offset loop older tutorials show is correct here even though it is not on Cloud.
The Brooked method
Brooked runs your JQL against the API, pages through the whole result set, resolves custom field IDs to their display names, and flattens the nested status and user objects into plain columns. Cloud, Server and Data Center are all supported, and several projects can land in one spreadsheet on a single schedule.
Step 1: Install Brooked
Install Brooked from the Google Workspace Marketplace. Free tier includes 100 imports per month, no credit card required.
Step 2: Connect Jira and write your JQL
Choose Jira in Brooked, enter your site URL, account email and API token. Then either pick a saved filter or paste JQL directly. Anything valid in the issue navigator is valid here, so project = ENG AND status changed to Done during (-14d, now()) works as written.
Step 3: Schedule the refresh
Set the import to re-run hourly, daily or weekly so sprint reports and dashboards rebuild themselves. Relative JQL dates like -30d re-evaluate on each run, so a rolling window stays rolling without anyone editing the query.
Method comparison
| Method | Cost | Setup | Refresh | Two-way | Best for |
|---|---|---|---|---|---|
| Jira CSV export | Free | 2 min | Manual | No | A one-off pull under 1,000 issues |
| Apps Script + REST API (JQL) | Free | 25 to 40 min | Time-driven trigger | DIY | Any issue count, custom JQL, no budget |
| Marketplace Sheets add-ons | Free tier · paid per user | 5 to 15 min | Scheduled (paid tiers) | Varies | Teams already buying Atlassian Marketplace apps |
| Brooked | Free tier · Pro $29/user/mo | ~3 min | Scheduled (hourly, daily, weekly) | Yes | Scheduled reporting across projects and boards |
Common use cases
- Sprint reporting: rebuild velocity and burndown tables that leadership reads in Sheets, not Jira
- Cycle time analysis: compare created against resolutiondate to measure how long work actually takes
- Cross-project roll-ups: report across several projects in one table, which Jira dashboards make awkward
- Bug triage: track open defects by component, priority and age with your own scoring columns
- Capacity planning: join story points against a team roster that already lives in the spreadsheet
Troubleshooting common issues
Related Articles
- How to Connect Monday.com to Google Sheets
- How to Connect Clockify to Google Sheets
- How to Connect ClickUp to Google Sheets
Frequently asked questions
Why does my Jira CSV export stop at 1,000 issues?
Jira's issue navigator export is capped, historically at 1,000 rows, and on Jira Cloud that limit is not something you can raise yourself. Splitting the export by date range or project works but gets painful quickly. The REST API has no such ceiling: you page through results with JQL and can pull tens of thousands of issues.
How do I connect Jira to Google Sheets for free?
Two free routes. Export a filtered view to CSV from the issue navigator and import the file, which takes about two minutes but caps out at 1,000 issues and never refreshes. Or call the Jira REST API from Apps Script with an API token, which handles any issue count and can run on a schedule, at the cost of about half an hour of setup.
What are the customfield_10016 columns in my export?
Jira stores custom fields under opaque numeric IDs rather than their display names, so story points might arrive as customfield_10016 and sprint as customfield_10020. The IDs differ between Jira instances, so a script that works on one site breaks on another. Call the /rest/api/3/field endpoint to get the ID-to-name mapping and translate your headers with it.
Which Jira search endpoint should I use?
Atlassian replaced the long-standing /rest/api/3/search endpoint with /rest/api/3/search/jql on Jira Cloud. The replacement uses token-based pagination via nextPageToken instead of startAt offsets, and it does not return a total issue count, which breaks progress bars built on the old response. Older tutorials still show the offset version; if you get deprecation warnings or unexpected responses, that is why.
Can I use this with Jira Server or Data Center?
Yes, with two differences. Self-hosted instances use /rest/api/2/ rather than /rest/api/3/, and they authenticate with a personal access token as a bearer token, or with basic auth on older versions, rather than the email-plus-API-token pairing Jira Cloud uses. The JQL and the pagination concepts are otherwise the same.
Can I write changes from Google Sheets back into Jira?
Yes for editable fields. Brooked supports write-back, so you can bulk-update summaries, assignees, labels or story points from a sheet. Jira will reject writes to fields that are computed or controlled by workflow: issue key, created date, and status transitions in particular, since status changes must go through a workflow transition rather than a field edit.
Connect Jira to Google Sheets today.
Free tier: 100 imports per month, no credit card required. Or see the full Jira integration details.
Install Brooked free →

