GitHub Discussions

GitHub Discussions is a built-in forum that lives alongside a repository’s Issues and Pull Requests. It gives a project a place for conversation that doesn’t fit either of those tools: questions from users, feature proposals, announcements from maintainers, polls, and general community chatter. Issues track a specific, closeable unit of work; Pull Requests review a specific set of commits against a branch; Discussions are open-ended threads that persist and get referenced over and over, closer to a forum than a to-do list. This lesson covers how Discussions are organized, how (and whether) they relate to your repository’s Git history, how to manage them from the command line and the API, and the conventions that keep a Discussions board useful instead of noisy.

Overview: How Discussions Fit Into a Repository

Every repository already has two conversational surfaces: comments on an Issue, and comments on a Pull Request. Both are tied to something concrete — an Issue is meant to end in a state change (closed, fixed, won’t-fix), and a Pull Request is meant to end in a merge or a close. Discussions add a third surface with no such expectation. A Discussion can stay open indefinitely, gets organized into categories instead of labels, and — in its Question and Answer format — supports marking one reply as the accepted answer, something neither Issues nor Pull Request comments can do.

It’s worth being explicit about what Discussions are not: they are not part of Git’s object model. A commit is a content-addressed object containing a pointer to a tree (a snapshot of the file hierarchy), the tree’s blobs (file contents), a pointer to its parent commit(s), and metadata, all identified by a SHA hash and stored under .git/objects. Branches and tags are just refs — files containing a commit SHA — that make those objects reachable. None of that machinery applies to a Discussion. A Discussion is a row in GitHub’s own database, associated with a repository the same way an Issue or a wiki page is: reachable by a URL and by the REST/GraphQL API, but never downloaded by git clone, never hashed into a commit, and not restored if you only back up the .git directory. If the repository is deleted, its Discussions go with it, but a full local clone of the repository’s history carries none of them.

Discussion Categories and Formats

When you enable Discussions on a repository, GitHub creates a starter set of categories. Each category has a format that controls what UI shows up on every post in it:

Category Default format Typical use
Announcements Open-ended (only maintainers can start threads) Release notes, roadmap updates
Q&A Question / Answer “How do I…” support questions
Ideas Open-ended Feature requests, brainstorming
Polls Poll Quick yes/no or multiple-choice votes
Show and tell Open-ended Things the community built with the project
General Open-ended Everything that doesn’t fit elsewhere

Maintainers can rename, delete, or add categories from the Discussions tab’s category settings, and can choose a category’s format when creating it. The Question and Answer format is the one structural feature worth understanding in depth: on any reply, the original poster (or anyone with write access) sees a “Mark as answer” control. Marking a reply pins it directly under the original post with a check mark and records an answerChosenAt timestamp on the discussion — this is what turns a Q&A thread from a flat chat log into something a future searcher can scan in five seconds.

How Discussions Compare to Issues and Pull Requests

Feature Issues Discussions Pull Requests
Purpose Track a specific, closeable task or bug Open-ended conversation, support, ideas Propose and review a specific code change
Open/closed state Yes No (only locked/unlocked and pinned/unpinned) Yes (open, closed, merged)
Can mark an authoritative resolution No Yes, in Q&A format Not directly — reviews approve or request changes
Tied to a commit or branch Only indirectly, via a linked PR No Always — reviews a diff between two refs
Organized by Labels, milestones, assignees Categories Labels, reviewers, requested changes

Syntax

There is no dedicated Git subcommand for Discussions — this is a GitHub product feature, not something stored in the repository’s Git history, so nothing in plain git touches it. As of this writing, the GitHub CLI (gh) also has no dedicated gh discussion subcommand; you manage Discussions from the web UI, or programmatically through the GitHub API. Two entry points matter:

  • gh repo edit OWNER/REPO --enable-discussions — toggles Discussions on for a repository you administer. The equivalent web path is the repository’s Settings tab, General section, Features checkboxes.
  • gh api graphql -f query='...' — sends a raw GraphQL query or mutation to GitHub’s API. Discussions have full read and write support in the GraphQL API (queries like repository.discussions and repository.discussionCategories, and mutations like createDiscussion, addDiscussionComment, and markDiscussionCommentAsAnswer), which makes GraphQL the right tool whenever you want to script something Discussions-related rather than click through the UI.

Relevant gh repo edit flags:

  • --enable-discussions / --enable-discussions=false — turn the Discussions tab on or off.
  • --enable-issues — the equivalent toggle for the Issues tab.
  • --enable-wiki — the equivalent toggle for the Wiki tab.
  • --enable-projects — the equivalent toggle for classic repository-level Projects.

Examples

Example 1: List a repository’s discussion categories. Before you can create a discussion through the API you need the category’s internal ID, since the createDiscussion mutation takes a categoryId, not a category name.

gh api graphql -f query='
query {
  repository(owner: "octocat", name: "hello-world") {
    discussionCategories(first: 10) {
      nodes {
        id
        name
        emoji
      }
    }
  }
}'

Output:

{
  "data": {
    "repository": {
      "discussionCategories": {
        "nodes": [
          { "id": "DIC_kwDOJj3x284CkL2z", "name": "Q&A", "emoji": ":raised_hand:" },
          { "id": "DIC_kwDOJj3x284CkL20", "name": "Ideas", "emoji": ":bulb:" },
          { "id": "DIC_kwDOJj3x284CkL21", "name": "General", "emoji": ":speech_balloon:" }
        ]
      }
    }
  }
}

Each category comes back with a GraphQL node ID (DIC_...). You’ll paste one of these IDs into a later mutation — GitHub’s GraphQL API almost always deals in these opaque IDs rather than names, precisely because names can be renamed without breaking automation that depends on the ID.

Example 2: List the most recent discussions and check which ones are still unanswered.

gh api graphql -f query='
query {
  repository(owner: "octocat", name: "hello-world") {
    discussions(first: 5, orderBy: {field: CREATED_AT, direction: DESC}) {
      nodes {
        title
        url
        category {
          name
        }
        answerChosenAt
      }
    }
  }
}'

Output:

{
  "data": {
    "repository": {
      "discussions": {
        "nodes": [
          {
            "title": "How do I run required status checks on PRs opened from forks?",
            "url": "https://github.com/octocat/hello-world/discussions/42",
            "category": { "name": "Q&A" },
            "answerChosenAt": null
          },
          {
            "title": "Roadmap for v2.0",
            "url": "https://github.com/octocat/hello-world/discussions/41",
            "category": { "name": "Announcements" },
            "answerChosenAt": null
          }
        ]
      }
    }
  }
}

The first result is a Q&A discussion where answerChosenAt is null — nobody has marked a reply as the answer yet, which is exactly the kind of thread a maintainer triaging support questions wants to find quickly. The second is an Announcements post, where answerChosenAt is always null because that category isn’t in Q&A format.

Example 3: Create a new Q&A discussion from the command line. This is the realistic, end-to-end case: you already have the repository’s node ID and the category ID from Example 1, and you want to script a discussion post instead of clicking “New discussion” in the browser.

gh api graphql -f query='
mutation {
  createDiscussion(input: {
    repositoryId: "R_kgDOJj3x2g",
    categoryId: "DIC_kwDOJj3x284CkL2z",
    title: "How do I run required status checks on PRs opened from forks?",
    body: "Our CI requires status checks before merge, but they do not trigger on pull requests opened from forks. What is the recommended repository setting?"
  }) {
    discussion {
      url
      number
    }
  }
}'

Output:

{
  "data": {
    "createDiscussion": {
      "discussion": {
        "url": "https://github.com/octocat/hello-world/discussions/43",
        "number": 43
      }
    }
  }
}

The mutation returns the URL and number of the newly created discussion, the same way gh issue create prints the new issue’s URL when it finishes. This pattern — query for IDs, then mutate using those IDs — is the shape of almost every GraphQL write against GitHub’s API.

How It Works Step by Step

Walking through the lifecycle of a Discussion end to end:

  • Enabling. Toggling Discussions on (via Settings or gh repo edit --enable-discussions) creates the default category set behind the scenes; nothing is written to the Git repository itself.
  • Starting a thread. Anyone with at least the access level the repository’s settings require picks a category and writes a title and body. If the category’s format is Q&A, every subsequent reply gets a “Mark as answer” affordance; if it’s Poll, the composer shows options and a duration instead of a plain text box.
  • Marking an answer. The original poster, or anyone with write access to the repository, can mark exactly one reply as the answer. This sets answerChosenAt and moves that reply into a highlighted “Answer” block under the original post — the rest of the thread stays in place below it, unlike an Issue where closing hides nothing.
  • Converting an Issue to a Discussion. From an Issue’s “…” menu, Convert to discussion copies the issue’s original post and its full comment thread into a new Discussion in the category you pick, then closes the original Issue and leaves a note linking to where it went. This conversion only runs one way — there is no equivalent “convert this Discussion back to an Issue” action, and the item gets a new number in the Discussions numbering sequence, distinct from the Issues sequence it came from.
  • Locking and pinning. A maintainer can lock a Discussion (stops new comments, same control as locking an Issue) or pin it (keeps it at the top of the Discussions list — useful for a “read this first” thread). Neither action changes the answered/unanswered state.
  • Storage. None of the above touches your working tree, the index, or .git/objects. No commit is created and no ref moves. Everything lives in GitHub’s application database and is reachable only through the website or the API — which is also why Discussions don’t show up in git log, don’t get included in a git bundle, and aren’t preserved by any Git-level backup.

Common Mistakes

Mistake 1: assuming gh has a native discussion command.

gh discussion create --title "Does GitHub Discussions support polls?" --body "Just checking before I enable it."

Output:

gh: unknown command "discussion" for "gh"

Why it’s wrong: the GitHub CLI does not ship a built-in discussion subcommand. Fix: use the web UI’s “New discussion” button, or drive the GraphQL API directly with gh api graphql and the createDiscussion mutation shown in Example 3.

Mistake 2: filing a genuine bug report as a Discussion. A user opens a “General” or “Q&A” discussion describing a real crash because that tab happened to be more visible than “Issues.” The report now has no labels, isn’t on any project board, and isn’t part of the backlog anyone triages — it can sit unnoticed indefinitely. Fix: keep “how do I…” questions in Q&A, but ask reporters to file reproducible bugs as Issues; if a Discussion turns out to describe a real bug, a maintainer should manually open an Issue that references it (there’s no automatic Discussion-to-Issue conversion, only Issue-to-Discussion).

Mistake 3: expecting to undo an Issue-to-Discussion conversion. After converting, a maintainer realizes the item actually needed labels and a milestone. There’s no “convert back” button, and the item’s number has already changed, so any external tooling or documentation that referenced the old issue number now points at a redirect, not the live thread. Fix: before converting, confirm the thread genuinely doesn’t need issue-tracker features (labels, assignees, milestones, project board columns) — if unsure, leave it as an Issue and simply move the conversation with a comment instead.

Mistake 4: assuming enabling Discussions retroactively organizes old content. Turning Discussions on does not scan existing Issues and move the support questions among them into Q&A automatically — every old thread that should really be a Discussion has to be converted by hand, one at a time.

Best Practices

  • Keep Announcements restricted to maintainers (the default) so the category stays a signal, not a chat.
  • Actively mark answers in Q&A threads — an unanswered-looking thread with a correct reply buried in comment 14 helps nobody who finds it later through search.
  • Pin a “Start here” or FAQ discussion in General so recurring first-time questions have somewhere to point.
  • Convert stale, unresolvable-as-a-task Issues into Discussions instead of just closing them, so the conversation stays searchable.
  • Keep the category list small and specific; a dozen overlapping categories fragments the board more than it organizes it — merge or delete categories that see no traffic.
  • Cross-reference related Issues and Pull Requests from a Discussion using #123 so GitHub renders the automatic link between them.
  • Script bulk Discussion work (creating, listing, or auditing answered/unanswered threads) through gh api graphql, since there is no dedicated gh subcommand and the REST API’s Discussions coverage is read-heavy compared to GraphQL.

Practice Exercises

Exercise 1. On a repository you own, enable Discussions, then open one thread in Ideas and one in Q&A. Reply to the Q&A thread (from a second account, or ask someone else to), then mark the best reply as the answer. Expected end state: two discussions exist, and the Q&A one shows a highlighted “Answer” block.

Exercise 2. Find or open a support-style Issue in one of your repositories — something that’s really a question, not a task — and convert it to a Discussion from the Issue’s “…” menu. Compare the Issues list and the Discussions list before and after. What happened to the original issue number, and where does it now point?

Exercise 3. Using the query pattern from Example 1, fetch the discussionCategories for a public repository you don’t maintain, and identify which of its categories are in Question/Answer format versus open-ended format. Hint: the format field isn’t in the sample query shown — you’ll need to add it to the GraphQL selection set (check GitHub’s GraphQL API docs for the exact field name on the DiscussionCategory type).

Summary

  • GitHub Discussions is a repository-level forum, separate from Issues and Pull Requests, meant for open-ended conversation rather than closeable work.
  • Discussions are pure GitHub application data — no commits, blobs, trees, or refs are involved, and git clone never transfers them.
  • Every Discussion belongs to a category (Announcements, Q&A, Ideas, Polls, Show and tell, General by default), and each category has a format — open-ended, Question/Answer, or Poll.
  • Only the Q&A format supports marking an accepted answer, which pins that reply and records answerChosenAt.
  • gh repo edit OWNER/REPO --enable-discussions toggles the feature on; there is no dedicated gh discussion subcommand, so scripting reads and writes goes through gh api graphql.
  • Converting an Issue to a Discussion is one-directional and assigns a new number — there’s no built-in way back.
  • Keep categories few and purposeful, mark answers, and reserve Issues for anything that needs labels, assignees, or a project board.