Pricing

How to Connect GitHub to Google Sheets

GitHubSaved SearchDateDocumentAmount07/01INV-40214,280.0007/02INV-40221,860.5007/05INV-40239,140.0007/08INV-40242,375.2507/09INV-40256,902.7507/12INV-40263,118.40BrookedABCDateDocumentAmount123456707/01INV-40214,280.0007/02INV-40221,860.5007/05INV-40239,140.0007/08INV-40242,375.2507/09INV-40256,902.7507/12INV-40263,118.40Google SheetsGitHub report
JW
James Whitfield

Export GitHub issues and pull requests to Google Sheets, including the reason the issues endpoint returns your PRs too and the Link header that drives pagination.

GitHub has no built-in CSV export for issues, so unlike most tools there is no two-minute no-code option to fall back on: the API is the route. It is a pleasant API to work with, with one trap that catches nearly everyone. The issues endpoint returns pull requests as well as issues, because GitHub models a PR as a kind of issue, which quietly doubles every count you produce.

GitHub to Google Sheets for free (Apps Script + REST API)

Create a personal access token under Settings → Developer settings → Personal access tokens. A fine-grained token scoped to the repositories you need with read access to issues and metadata is the safer choice; a classic token with the repo scope also works. Always authenticate even for public repositories: unauthenticated requests are capped at 60 an hour against 5,000 for a token.

Two structural details. Pagination lives in the Link response header rather than the body, so you follow the URL marked rel="next" until it disappears. And the state parameter defaults to open, so any report about completed work needs state=all or state=closed set explicitly.

Code.gs
function importGitHubIssues() {  const TOKEN = "github_pat_XXXXXXXXXXXX";  const OWNER = "your-org";  const REPO  = "your-repo";   const headers = {    Authorization: "Bearer " + TOKEN,    Accept: "application/vnd.github+json",    "X-GitHub-Api-Version": "2022-11-28",  };   const rows = [];  // state defaults to "open"; anything historical needs state=all.  let url = "https://api.github.com/repos/" + OWNER + "/" + REPO    + "/issues?state=all&per_page=100";   while (url) {    const response = UrlFetchApp.fetch(url, { headers, muteHttpExceptions: true });    if (response.getResponseCode() !== 200) {      throw new Error(response.getResponseCode() + ": " + response.getContentText());    }     JSON.parse(response.getContentText()).forEach((item) => {      // A pull request IS an issue in GitHub's model, and comes back from      // this endpoint. Only PRs carry a pull_request object, so use it to      // separate them rather than double-counting issues.      if (item.pull_request) return;       rows.push([        item.number,        item.title,        item.state,        item.user ? item.user.login : "",        (item.assignees || []).map((a) => a.login).join(", "),        (item.labels || []).map((l) => l.name).join(", "),        item.created_at,        item.closed_at || "",        item.comments,        item.html_url,      ]);    });     // Pagination is in the Link header, not the JSON body.    url = nextLink(response.getHeaders());    if (url) Utilities.sleep(200);  }   const header = ["#","Title","State","Author","Assignees","Labels",                  "Created","Closed","Comments","URL"];  const ss = SpreadsheetApp.getActiveSpreadsheet();  const sheet = ss.getSheetByName("Issues") || ss.insertSheet("Issues");  sheet.clearContents();  sheet.getRange(1, 1, 1, header.length).setValues([header]);  if (rows.length) {    sheet.getRange(2, 1, rows.length, header.length).setValues(rows);  }} // Link: <https://api.github.com/...&page=2>; rel="next", <...>; rel="last"function nextLink(headers) {  const link = headers.Link || headers.link;  if (!link) return null;  const match = /<([^>]+)>;\s*rel="next"/.exec(link);  return match ? match[1] : null;}

To report on pull requests instead, invert the filter and keep only items with a pull_request key, or use the dedicated /pulls endpoint, which returns merge state and base and head branches that the issues endpoint does not include.

Limits: Apps Script's six-minute cap applies, so a repo with thousands of issues may need splitting with the since parameter. Reviews, review comments and commit details each require additional calls per item, which is where GraphQL starts to pay off.

The Brooked method

Brooked separates issues from pull requests correctly, follows the Link header pagination, and flattens labels, assignees and reviewers into readable columns. Several repositories can import into one spreadsheet on a single schedule, which is what makes cross-repo engineering reporting practical.

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 GitHub

Choose GitHub in Brooked and authorise, or paste a personal access token. Pick the repositories and whether you want issues, pull requests, commits or releases, then import.

Step 3: Schedule the refresh

Set the import to re-run daily or weekly so engineering metrics rebuild themselves. A scheduled re-read also picks up issues closed or reopened after the fact, which an event-driven feed would not revisit.

BrookedBrooked AI, GitHub
Live
Ask anything about your GitHub data…

Method comparison

MethodCostSetupRefreshTwo-wayBest for
GitHub CSV exportFreen/aNot availableNoNothing, GitHub has no built-in CSV export for issues
Apps Script + REST APIFree20 to 30 minTime-driven triggerDIYA few repos, no budget
GitHub Actions writing to SheetsFree tier minutes30 to 60 minOn schedule or on eventNoTeams already living in Actions
BrookedFree tier · Pro $29/user/mo~3 minScheduled (hourly, daily, weekly)NoScheduled engineering reporting

Common use cases

  • Cycle time: measure time from PR opened to merged across repos
  • Issue triage: track open bugs by label, age and assignee in one table
  • Release notes: pull merged PRs between two dates as a starting draft
  • Contributor reporting: activity per author across several repositories
  • SLA tracking: find issues open past a threshold and alert on them

Troubleshooting common issues

Frequently asked questions

Why does the GitHub issues endpoint return pull requests?

Because in GitHub's data model a pull request is an issue with extra attributes, so /repos/{owner}/{repo}/issues returns both. Every PR in the response carries a pull_request object that plain issues lack, so filter on the presence of that key. Skipping this step roughly doubles your issue counts on an active repo and is the single most common GitHub API mistake.

Can I export GitHub issues to Google Sheets for free?

Yes, via the API. GitHub has no built-in CSV export for issues, so there is no no-code route here at all. The Apps Script method in this guide uses a free personal access token and a free time-driven trigger. Brooked's free tier covers 100 imports a month if you would rather not maintain a script.

How does GitHub pagination work?

Unlike most APIs, GitHub does not return pagination state in the response body. It sends a Link header containing URLs with rel values such as next and last, and you follow the next link until it is absent. per_page maxes at 100. Code that expects a next_cursor field in the JSON will silently stop after the first page.

What are GitHub's rate limits?

5,000 requests per hour for an authenticated personal access token, versus only 60 per hour unauthenticated, which is why you should always authenticate even for public repos. The search API is limited separately and much more tightly, at around 30 requests per minute, so prefer the list endpoints with filters over search where you can.

Should I use REST or GraphQL?

REST is simpler and fine for straightforward exports of issues, PRs or commits. GraphQL earns its complexity when you need related data in one round trip, such as pull requests with their reviews, review comments and linked issues, where REST would need several calls per PR and burn through your rate limit.

Can I write back to GitHub from Sheets?

Technically yes with a write-scoped token, though it is rarely a good idea. Bulk-editing issue titles, labels or assignees from a spreadsheet bypasses code review and audit expectations that teams usually want around their tracker. Brooked treats GitHub as read-only for reporting.

Connect GitHub to Google Sheets today.

Free tier: 100 imports per month, no credit card required. Or see the full GitHub integration details.

Install Brooked free →

Also in Monitoring & Alerts

More Monitoring & Alerts guides

Monitoring & Alerts

How to Set Up Data Alerts in Google Sheets

The four working ways to alert on Sheets changes, Tools > Notification rules (any account), Conditional Notifications (Workspace, 20-rule limit since Sep 2025), Apps Script with onEdit for real-time, and time-driven triggers for threshold checks, plus Slack webhook integration and the three non-triggering scenarios with fixes.

JW
James Whitfield
Read

Get your spreadsheet hours back

Brooked sets up in seconds. Your team is querying live data before lunch.

Get started free