> ## Content Index
> Fetch the complete content index at: https://dees.co/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Create a "Browse by Topic" Page for Your Ghost Blog
- URL: https://dees.co/how-to-create-a-browse-by-topic-page-for-your-ghost-blog/
- Published: 2026-10-13T19:50:12.000Z
- Updated: 2026-10-13T19:50:12.000Z
- Description: Renders a linked list of every public tag on the site into a published Ghost page.
- Author: Garrett Dees
- Tags: Ghost

Ghost gives every tag its own archive page, but there's no built-in way to show visitors *all* your tags in one place. This post describes how to create a page on your site listing every public tag as a clickable link with post counts, sorted alphabetically. The best part: no theme edits required, so a theme update can't wipe out your work.

## Step 1: Get a Content API Key

Content API keys are read-only: they can only fetch your already-published content, which is exactly what public site visitors can already see. Ghost designs them to be safe for use in client-side JavaScript, which is what makes this whole approach possible.

1. Log into your Ghost Admin
2. Go to **Settings > Integrations**
3. Scroll down to **Custom Integrations** and click **Add custom integration**
4. Name it something memorable, like "Tags Page"
5. Copy the **Content API key**

## Step 2: Create the Page

1. Go to **Pages > New page**
2. Give it a title like "Browse by Topic"
3. In the page settings panel, check the **URL** and set it to `browse-by-topic` (or anything you like; whatever you choose goes into the script's `SLUG` variable in Step 3)
4. Add some placeholder text like "Loading tags..."
5. Publish the page

## Step 3: Add the Script

Go to **Page settings > Code Injection** and paste this into the P**age footer**:

```html
<!-- Renders a linked list of every public tag on the site into a published Ghost page. -->

<script>
  (function() {
    var KEY = 'YOUR_CONTENT_API_KEY';  // Content API key
    var SLUG = 'browse-by-topic';      // URL of the page this script should run on
    var SELECTOR = '.gh-content';      // adjust to your theme's page-body class

    // Escape regex metacharacters in the slug
    var escapedSlug = SLUG.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    var pattern = new RegExp('^\\/' + escapedSlug + '\\/?$');
    if (!pattern.test(window.location.pathname)) return;

    var url = window.location.origin + '/ghost/api/content/tags/' +
      '?key=' + KEY + '&limit=all&include=count.posts&order=name%20ASC';

    fetch(url)
      .then(function(res) {
        if (!res.ok) throw new Error('HTTP ' + res.status + ' ' + res.statusText);
        return res.json();
      })
      .then(function(data) {
        var tags = data.tags || [];
        var el = document.querySelector(SELECTOR);
        if (!el) {
          console.warn('[tags-page] Selector "' + SELECTOR + '" not found');
          return;
        }

        el.innerHTML = '';  // remove the placeholder text

        var ul = document.createElement('ul');
        ul.className = 'all-tags-list';

        tags.forEach(function(tag) {
          // Skip tags with no public posts
          if (!tag.count || !tag.count.posts) return;

          var li = document.createElement('li');
          var a = document.createElement('a');
          a.href = tag.url;
          a.textContent = tag.name + ' (' + tag.count.posts + ' posts)';
          li.appendChild(a);
          ul.appendChild(li);
        });

        el.appendChild(ul);
        console.log('[tags-page] Rendered ' + ul.children.length + ' tags');
      })
      .catch(function(err) {
        console.error('[tags-page] Failed:', err);
      });
  })();
</script>

```

Make three adjustments, all in the config block at the top of the script:

- **`KEY`**: paste in your Content API key
- **`SLUG`**: set this to the URL slug of the page you created in Step 2
- **`SELECTOR`**: your theme's page-content class (covered in Step 4)

## Step 4: Find Your Theme's Content Class

The `SELECTOR` variable tells the script where to inject the list. Most modern Ghost themes use `.gh-content` for page body content, but yours may differ.

To check: open your new page, press **F12** to open DevTools, right-click your placeholder text, and choose **Inspect**. Look at the class on the wrapping element. If you see something like `page-content` or `post-content` instead, swap it into the `SELECTOR` line.

## A note for crawlers

The tag list is rendered client-side, so it only exists once JavaScript runs. Human visitors get the full page, but many search engine and AI crawlers don't execute JavaScript and will only ever see your placeholder text. I ran into this firsthand: an AI crawler indexed my own tags page with the description "Loading tags...".

If being legible to crawlers matters to you, keep a manual list of your tags as permanent page content (a plain bulleted list of links). The script replaces it with the live version for human visitors, so nobody sees a stale list, but crawlers always have something real to read. The tradeoff is updating the manual list when you add or remove a tag.

## Troubleshooting

If the page stays empty, open DevTools (F12, Console tab) and check what the script logged:

| HTTP 401/403                                             | Key is wrong, or you copied the Admin API key instead of the Content API key                 | Re-copy the Content API key from your custom integration                             |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Selector ".gh-content" not found                         | Theme wraps page content in a different class                                                | Swap the class into SELECTOR (see Step 4)                                            |
| Nothing logged at all                                    | SLUG doesn't match your page's URL                                                           | Verify the URL in the page settings panel                                            |
| A tag is missing from the list                           | Internal tags (names starting with #) are excluded by design; or the tag has no public posts | Neither is a bug — internal tags aren't publicly visible, and empty tags are skipped |
| Tag list loads but the API call uses the wrong base path | Ghost installed under a subdirectory, not the domain root                                    | Change the API URL to include your subdirectory                                      |