Transition From Disqus to giscus

Moving my blog's comments from Disqus to giscus: enabling GitHub Discussions, exporting old comments, and importing them with a small script.

Published on The word Disqus struck through, an arrow pointing to a green giscus speech bubble labelled 'powered by GitHub Discussions'

For a long time I’ve wanted to move away from Disqus to giscus, for a few reasons. Back when I started this blog, Disqus was a convenient way to add comments. But there are plenty of reports about how invasive Disqus is: how much tracking it injects into a page, and how much the platform is disliked in general.

The post that finally pushed me over the edge was Rob J. Hyndman’s Moving from Disqus to giscus. I found it a while ago and it stuck with me.

So here I am, staring at a pile of work to enable giscus for my blog. Let’s go.

This is a multi-step process

Here’s the rough shape of it:

  1. Enable Discussions in my GitHub repo
  2. Generate the giscus config values
  3. Export the Disqus comments
  4. Fix the URLs in the export
  5. Import the comments into GitHub Discussions
  6. Update the Hugo config
  7. Replace the comments template
  8. Verify comments show up on the blog

Enable Discussions in my GitHub repo

  1. Repo Settings → General → Features → check “Discussions”.
  2. Install the giscus GitHub App: https://github.com/apps/giscus → Configure → grant it to ingorichter/ingorichter.github.io only. Grant Read access to metadata and Read and write access to discussions. This step requires authentication.
  3. The ingorichter/ingorichter.github.io repo must be public. Check!
  4. Create a Discussions category for comments:
    • Repo Discussions → Categories (pencil icon) → New category.
    • Name: Comments. Type: Announcements (only maintainers open threads — giscus opens them via the app, visitors only reply). Format: Open-ended discussion, but this can’t be selected — it’s the default for this type.

Generate the giscus config values

Go to https://giscus.app and fill in the form:

Copy the generated script. It’s used later in the Hugo comments.html:

 1<script src="https://giscus.app/client.js"
 2        data-repo="ingorichter/ingorichter.github.io"
 3        data-repo-id="MDEwOlJlcG9zaXRvcnkxMDgwNjQ0Mw=="
 4        data-category="Comments"
 5        data-category-id="DIC_kwDOAKTkq84DEfQE"
 6        data-mapping="pathname"
 7        data-strict="1"
 8        data-reactions-enabled="1"
 9        data-emit-metadata="0"
10        data-input-position="top"
11        data-theme="preferred_color_scheme"
12        data-lang="en"
13        data-loading="lazy"
14        crossorigin="anonymous"
15        async>
16</script>

Export the Disqus comments

I wanted to keep as many of the Disqus comments as possible. There are only a few, but they matter to me.

  1. Disqus admin → your site → Community → Export (or https://<shortname>.disqus.com/admin/discussions/export/). Shortname: ingorichterio.
  2. Wait for the email → download <shortname>-<date>.xml.gzgunzip it to .xml.

The email arrived fast.

Fix the URLs in the Disqus export

I asked Claude to help me identify which comments are still live and worth saving. After some back and forth, I had a cleaned-up, minimal XML file to use in the next step.

Import into GitHub Discussions

I used Claude to help me write an importer script that consumes the Disqus export file.

It talks to GitHub through the gh CLI, so it needs the read:discussion and write:discussion scopes on my gh login. I had to run gh auth refresh -s write:discussion,read:discussion before running the script:

1# dry run — already ran, output looked good
2python3 import-to-giscus.py --repo ingorichter/ingorichter.github.io --category Comments --dry-run
3
4# real run
5python3 import-to-giscus.py --repo ingorichter/ingorichter.github.io --category Comments

The script creates one Discussion per thread (title = pathname) and posts each Disqus comment on it, preserving the author name and date in the body. Replies stay threaded under their parent comment.

I improved the import script until it was idempotent and added an option to wipe all previously imported comments. That helps a lot while testing, right up until everything goes live.

GitHub’s Discussion search index lags a minute or two behind. If you re-run the script right away, it might not see the Discussions it just created. Wait a bit between runs.

Update the Hugo config

Remove the Disqus config from config.toml:

1[services.disqus]
2shortname = 'ingorichterio'

Add this block instead. The repoID and categoryID are part of the <script> generated on the giscus site (see above):

 1[params.giscus]
 2repo = "ingorichter/ingorichter.github.io"
 3repoID = "MDEwOlJlcG9zaXRvcnkxMDgwNjQ0Mw=="
 4category = "Comments"
 5categoryID = "DIC_kwDOAKTkq84DEfQE"
 6mapping = "pathname"
 7strict = "1"
 8reactionsEnabled = "1"
 9inputPosition = "top"
10theme = "preferred_color_scheme"

Replace the comments template

I needed a new template for Hugo to render the giscus comment section on each post, so I added a new file at layouts/_default/comments.html:

 1{{- $g := .Site.Params.giscus -}}
 2{{- if and $g $g.repo $g.repoID (ne .Params.comments false) (not .Params.menu) -}}
 3  <section class="comments">
 4    <h2>Comments</h2>
 5    <script src="https://giscus.app/client.js"
 6      data-repo="{{ $g.repo }}"
 7      data-repo-id="{{ $g.repoID }}"
 8      data-category="{{ $g.category }}"
 9      data-category-id="{{ $g.categoryID }}"
10      data-mapping="{{ default "pathname" $g.mapping }}"
11      data-strict="{{ default "1" $g.strict }}"
12      data-reactions-enabled="{{ default "1" $g.reactionsEnabled }}"
13      data-emit-metadata="0"
14      data-input-position="{{ default "top" $g.inputPosition }}"
15      data-theme="{{ default "preferred_color_scheme" $g.theme }}"
16      data-lang="en"
17      data-loading="lazy"
18      crossorigin="anonymous"
19      async>
20    </script>
21    <noscript>Enable JavaScript to view comments.</noscript>
22  </section>
23{{- end -}}

Now .Render "comments" in single.html picks it up unchanged. Opt out per post with comments: false in the front matter.

Verify comments show up on the blog

Run make preview to launch the local Hugo server.

One comment came through mapped to the wrong thread. I fixed the script, wiped the imported comments (the reset option earned its keep here), and re-ran the import with the corrected Disqus export XML. That sorted it out.

Done

After about an hour of work, I cut ties with Disqus. giscus works just fine, and I’m happy to run something less invasive.

Thanks to the maintainers for a great piece of open source software.

Mahalo 🌸

#TIL  

Comments