# Comments with giscus

> Add GitHub-backed comments with giscus.

---

LLMS index: [llms.txt](/llms.txt)

---

OINK supports [giscus][] through a [Hextra-compatible comments configuration][]
under `params.comments`. giscus gives each content page a comment thread backed
by [GitHub Discussions][] and lets readers comment through GitHub OAuth.

## How giscus works {#how-giscus-works}

When a page loads, giscus searches the configured repository for a Discussion
that matches the page. If it does not find one, the giscus bot creates it when a
reader submits the first comment or reaction. Maintainers moderate comments in
GitHub Discussions.

Anyone can read a public thread. To comment, a reader selects **Sign in with
GitHub** and authorizes the giscus app to post on their behalf. OINK never asks
for or stores the reader's GitHub password or access token.

> [!IMPORTANT] External service
>
> giscus is an opt-in online integration. Active comment pages load a script and
> iframe from `https://giscus.app`; they do not work in a network-isolated
> runtime. Document this external boundary in the consuming site's privacy and
> security policy when required.

## Prepare GitHub {#prepare-github}

Before configuring OINK:

1. Use a **public** GitHub repository for the comment threads. Visitors cannot
   read Discussions in a private repository.
2. [Enable GitHub Discussions][] under the repository's **Settings > Features**.
3. Install the [giscus GitHub App][] for that repository. Without the app,
   visitors cannot comment or react.
4. Choose a Discussion category. giscus recommends an **Announcements** category
   so that only maintainers and the giscus bot can create new Discussions.

The repository ID and category ID are public identifiers, not credentials. Do
not add a GitHub personal access token, OAuth secret, or password to Hugo
configuration.

## Generate repository settings {#generate-repository-settings}

Open [giscus.app][giscus] and complete its configuration form:

1. Select the interface language.
2. Enter the repository as `OWNER/REPOSITORY` and wait for the validation to
   succeed.
3. Select the page-to-Discussion mapping. `pathname` is OINK's default.
4. Choose the Discussion category and optional features.
5. Locate the generated `<script>` block.

Copy these generated values into OINK configuration:

| Generated attribute | OINK key     |
| ------------------- | ------------ |
| `data-repo`         | `repo`       |
| `data-repo-id`      | `repoId`     |
| `data-category`     | `category`   |
| `data-category-id`  | `categoryId` |

### Choose a stable mapping {#choose-a-stable-mapping}

The mapping determines which Discussion belongs to each page. `pathname` is a
good default when published paths are stable and the same repository serves
multiple domains or preview environments.

Changing `mapping`, moving a page, or changing its permanent URL can make giscus
look for a different Discussion. Choose the mapping before collecting comments
and preserve redirects or Discussion titles during a migration. Enable strict
matching when similar page paths could otherwise select the wrong thread.

## Enable comments site-wide {#enable-comments-site-wide}

Add the generated identifiers to the consuming site's `hugo.yml` and set
`enable: true`:

```yaml
params:
  comments:
    enable: true
    type: giscus
    giscus:
      repo: OWNER/REPOSITORY
      repoId: REPOSITORY_ID
      category: Announcements
      categoryId: CATEGORY_ID
      mapping: pathname
      strict: 0
      reactionsEnabled: 1
      emitMetadata: 0
      inputPosition: top
      theme: auto
      loading: lazy
```

Replace all uppercase placeholders with the exact values generated by
giscus.app. OINK requires `repo`, `repoId`, `category`, and `categoryId` before
it renders giscus. Missing or blank required values produce a Hugo warning and
skip giscus instead of failing the build.

## Configuration reference {#configuration-reference}

| Key                | Default         | Purpose                                                                |
| ------------------ | --------------- | ---------------------------------------------------------------------- |
| `enable`           | `false`         | Enables the configured comment provider globally.                      |
| `type`             | `giscus`        | Selects giscus. Other provider names are not supported.                |
| `repo`             | —               | Public repository in `OWNER/REPOSITORY` form.                          |
| `repoId`           | —               | Repository node ID generated by giscus.app.                            |
| `category`         | —               | GitHub Discussions category name.                                      |
| `categoryId`       | —               | Category node ID generated by giscus.app.                              |
| `mapping`          | `pathname`      | Maps the current page to a Discussion.                                 |
| `term`             | —               | Supplies the term required by mappings such as `specific` or `number`. |
| `strict`           | `0`             | Uses strict Discussion-title matching when set to `1`.                 |
| `reactionsEnabled` | `1`             | Shows reactions for the Discussion's main post.                        |
| `emitMetadata`     | `0`             | Sends Discussion metadata messages to the parent page.                 |
| `inputPosition`    | `top`           | Places the comment editor at `top` or `bottom`.                        |
| `theme`            | `auto`          | Follows the OINK theme, or selects a built-in/custom giscus theme.     |
| `lang`             | Page language   | Overrides the automatically selected giscus interface language.        |
| `loading`          | `lazy`          | Defers iframe loading until the reader approaches the comments.        |
| `ariaLabel`        | `Comments`      | Labels the comments region for assistive technology.                   |
| `errorMessage`     | Load-error text | Replaces the message shown when giscus cannot load.                    |

Boolean-like feature values accept YAML booleans or giscus-style `0` and `1`
values.

## Locale, theme, and accessible text {#locale-theme-and-accessible-text}

OINK selects the giscus locale from the active Hugo language. Simplified,
Traditional, and Hong Kong Chinese map to the corresponding giscus locales;
unsupported languages fall back to English. Set `lang` only when the automatic
choice is not appropriate.

With `theme: auto`, the iframe follows OINK's light/dark selector and the
browser's preferred color scheme. A built-in giscus theme name or custom theme
URL disables that automatic switch.

For a multilingual site, localize the comments-region label and load-error text
under each language's parameters. Language parameters merge with the global
repository settings:

```yaml
languages:
  en:
    params:
      comments:
        giscus:
          ariaLabel: Comments
          errorMessage: Comments could not be loaded.
  zh:
    params:
      comments:
        giscus:
          ariaLabel: 评论
          errorMessage: 评论加载失败。
```

## Override one page {#override-one-page}

The `comments` front matter field overrides the global switch in either
direction.

### Enable one page {#enable-one-page}

Keep the complete repository configuration in `hugo.yml`, leave the global
switch off, and opt in selected pages:

```yaml
---
title: Community design notes
comments: true
---
```

### Disable one page {#disable-one-page}

When comments are enabled globally, opt out pages that should remain static:

```yaml
---
title: Security policy
comments: false
---
```

An explicit `comments: false` suppresses both giscus and legacy Disqus on that
page.

## Coexist with Disqus {#coexist-with-disqus}

OINK keeps existing Hugo Disqus configuration compatible during migration. When
valid giscus configuration is active for a page, OINK suppresses Disqus so that
only one comment system renders. If giscus is enabled but its required settings
are incomplete, OINK warns, skips giscus, and can leave configured Disqus as a
fallback.

Remove the Disqus service configuration after the migration is complete and
every intended page uses giscus.

## Content Security Policy {#content-security-policy}

A strict Content Security Policy must permit giscus in both `script-src` and
`frame-src`. Merge these sources into the site's existing policy instead of
replacing its other directives:

```text
script-src 'self' https://giscus.app;
frame-src 'self' https://giscus.app;
```

OINK's initializer remains a same-origin bundled asset and is included only on
pages where giscus is active. If the external script fails or does not create an
iframe, OINK clears the loading state and exposes `errorMessage` in a live
status region.

## Verify the integration {#verify-the-integration}

1. Build the site and confirm there is no missing-key warning:

   ```sh
   hugo --minify
   ```

2. Start a local preview and open a page where comments should be active:

   ```sh
   hugo server --disableFastRender
   ```

3. Confirm that the giscus iframe shows **Sign in with GitHub** and uses the
   active page language.
4. Toggle OINK between light and dark themes and confirm that the comment widget
   follows it when `theme: auto`.
5. Open a page with `comments: false` and confirm that it has no giscus or
   Disqus widget.
6. Submit one test comment, then confirm that the expected Discussion appears in
   the configured category and can be moderated on GitHub.

A browser-console message saying that the Discussion was not found is expected
before the first comment or reaction creates it.

## Troubleshooting {#troubleshooting}

- **The build warns about missing keys:** regenerate the configuration at
  giscus.app and copy all four required identifiers without renaming them.
- **The widget does not appear:** check `params.comments.enable`,
  `params.comments.type`, the page's `comments` front matter, and Hugo's warning
  output.
- **GitHub sign-in or posting fails:** confirm that the repository is public,
  Discussions are enabled, and the giscus GitHub App is installed for the
  repository.
- **The browser blocks giscus:** inspect the console and response headers, then
  allow `https://giscus.app` in the applicable CSP directives.
- **An existing thread is not found:** restore the original mapping and page
  path, or rename/migrate the Discussion deliberately before changing the URL.
- **The interface language is wrong:** verify the Hugo language name and locale,
  or set `params.comments.giscus.lang` explicitly.

[Enable GitHub Discussions]:
  https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/enabling-or-disabling-github-discussions-for-a-repository
[giscus]: https://giscus.app/
[giscus GitHub App]: https://github.com/apps/giscus
[GitHub Discussions]: https://docs.github.com/en/discussions
[Hextra-compatible comments configuration]:
  https://imfing.github.io/hextra/docs/advanced/comments/
