GitHub Issues to dashboard

Issues from the API, charted without a script.

Pull your issues with the gh CLI or the REST API, save them as CSV, and drop the file here. Three things get handled on arrival: pull requests are removed from the issue counts, because the issues endpoint returns both unless you filter, rows with a blank closed_at are treated as open rather than as missing data, and the comma-joined labels and assignees columns are split into individual values you can group by.

Prefer the full explorer? Open the app, or start from the generic dashboard builder.

Pull requests separated
PR rows are identified by their URL and kept out of the issue counts.
Open issues detected
A blank closed_at means still open, and the age chart is built from that.
Labels split
Comma-joined labels and assignees become individual, groupable values.
Local only
Private repository issue titles are parsed in your tab and never transmitted.

The issues endpoint returns pull requests too

This is the trap that has caught every engineer who ever tried to count their repository's open issues. GitHub models a pull request as a kind of issue, so the issues endpoint returns both, and a repository with a healthy review culture can be more than half pull requests. Your open issue count doubles, your median time to close collapses because PRs close far faster than issues, and the resulting chart tells a story about review speed while claiming to be about bug triage.

Filtering them out is easy once you know: pull requests carry a pull_request object that plain issues do not, so a flattened export can drop them on that field or on the URL shape. What is not easy is noticing in the first place, because nothing about the numbers looks wrong. This page checks the URL column for the pull path and separates the two automatically, telling you how many rows it moved.

How to export from GitHub Issues

  1. Install and authenticate the gh CLI Run gh auth login once. The CLI handles token scopes for you, which is considerably less fiddly than minting a personal access token by hand for a one-off export.
  2. List the issues you want Something like gh issue list with an explicit state of all and a generous limit. The default limit is 30, which is small enough that people build entire reports on their thirty most recent issues without noticing.
  3. Ask for the fields you need Request number, title, state, author, assignees, labels, milestone, comments, createdAt, updatedAt, closedAt and url. Reactions need a preview header on the REST API, so grab those separately if you want them.
  4. Convert the result to CSV The CLI emits JSON. Flatten the labels and assignees arrays to comma-joined strings, or run the JSON through the converter linked below, which does exactly that.
  5. Drop the CSV here The PR split happens first, then labels become chips, and issue age is drawn from created_at against closed_at or today.

The REST API paginates at 100 results per page. A repository with a long history needs several requests joined together, and forgetting the page parameter is another quiet way to end up reporting on a hundred issues out of two thousand.

Try with sample GitHub Issues data (the same columns, 100 rows, no upload needed).

What the dashboard shows

Every panel below is built from a column that is actually in the export, named the way GitHub Issues names it.

  • Open and closed over time. created_at and closed_at as two series, which is the classic open-versus-closed chart that shows whether the backlog is actually growing.
  • Time to close. closed_at minus created_at for resolved issues, shown as a distribution, with pull requests excluded so review speed does not flatter it.
  • Label breakdown. Split from the comma-joined column, so bug, good-first-issue and needs-triage each count properly instead of forming combined pseudo-labels.
  • Backlog age. How long open issues have been open, ranked, which surfaces the eighteen-month-old bug everyone has stopped seeing.
  • Milestone progress. Closed against open per milestone, with unmilestoned issues as their own group since that bucket is usually the largest.
  • Engagement. Comment counts and reaction counts ranked, which is the closest proxy a public repository has for what users actually care about.
Sample header row
number,title,state,author,assignees,labels,milestone,comments,reactions,created_at,updated_at,closed_at,url

GitHub issue export details that matter

  • Pull requests come back as issues. The issues endpoint returns both by design. On an active repository the PRs can outnumber real issues, and they close much faster, so every duration metric shifts if you leave them in.
  • closed_at is blank for open issues. There is a state column too, but any tool that drops empty cells removes your open issues entirely and leaves a file where the backlog appears to be zero.
  • Labels and assignees are arrays. In the API they are lists of objects. Flattened to CSV they become comma-joined strings, so an issue with three labels reads as one long value until something splits it.
  • Reactions need a preview header. The reactions count is not returned by default on the REST API. Without the right Accept header the column is simply absent, which looks like a repository where nobody reacts to anything.
  • Pagination stops at 100. Both the API and the CLI cap a single response. Without paging or an explicit limit you get the most recent page and a report that silently covers a fraction of the history.
  • The author can be a bot. Dependabot and other automation open real issues with real timestamps. Left in, they dominate the author ranking and make your team look less active than it is.

GitHub's issue data is clean and consistently formatted, which is more than most exports manage. The work is all in deciding what belongs in the file: no pull requests, no bots if you are measuring people, and every page of results rather than the first one.

Frequently asked questions

Why does my issue count include pull requests?

Because GitHub treats a pull request as a type of issue, so the issues endpoint returns both. On a repository with an active review culture, more than half the rows can be PRs. They also close much faster than real issues, so leaving them in shortens your median time to close dramatically. This page separates them using the URL column and reports how many it moved.

How do I know which issues are still open?

Two ways, and they should agree. The state column says open or closed, and closed_at is blank for anything still open. Checking both is worth the effort, because a mismatch usually means the export was built from a cached or partial pull. The age chart here is built from created_at against closed_at, falling back to today for open rows.

Why are my labels grouping into odd combinations?

Labels is an array in the API and a comma-joined string once it is flattened to CSV. An issue labeled bug and regression exports as one value containing both, which a plain group-by treats as a distinct third label. The parser splits on the separator and counts each label individually, so the label totals will exceed the issue count, which is the correct behavior.

How many issues can I export at once?

The REST API returns 100 per page and the gh CLI defaults to a limit of 30, which is the more dangerous of the two because it is so small. Pass an explicit high limit to the CLI, or page through the API and concatenate the results. A report built on the default thirty most recent issues looks completely plausible and describes almost nothing.

Why is the reactions column missing?

Reaction counts require a preview Accept header on the REST API, and without it the field is simply not returned. There is no error, just an absent column, so it reads as a repository where nobody has ever reacted to anything. If reactions matter for your prioritization, add the header to the request or fetch them in a second pass keyed on the issue number.

Is private repository data safe here?

Yes, because it never leaves the tab. Issue titles from a private repository frequently describe unpatched security problems, customer escalations and unreleased features, which is a strong reason not to paste them into an online converter. The file is parsed by JavaScript in your browser, the charts render from memory, and closing the tab is the entire cleanup.

Chart your backlog without writing a script

100 rows in the exact shape the API returns, pull requests mixed in with issues, comma-joined labels, and open rows carrying a blank closed_at.

Open the sample GitHub Issues dashboard