facebook/docusaurus · error

Duplicate permalinks found in tags file: ${duplicateList}

Error message

Duplicate permalinks found in tags file:
${duplicateList}

What it means

Thrown by ensureUniquePermalinks() after a tags file (tags.yml) is parsed and normalized. The function iterates every tag entry, collects permalinks into a Set, and if any permalink value appears more than once it raises this error listing the offending permalinks. Permalinks become URLs for tag listing pages, so collisions would produce ambiguous routing and are rejected up front.

Source

Thrown at packages/docusaurus-utils-validation/src/tagsFile.ts:47

export function ensureUniquePermalinks(tags: TagsFile): void {
  const permalinks = new Set<string>();
  const duplicates = new Set<string>();

  for (const [, tag] of Object.entries(tags)) {
    const {permalink} = tag;
    if (permalinks.has(permalink)) {
      duplicates.add(permalink);
    } else {
      permalinks.add(permalink);
    }
  }

  if (duplicates.size > 0) {
    const duplicateList = Array.from(duplicates)
      .map((permalink) => `  - ${permalink}`)
      .join('\n');
    throw new Error(
      `Duplicate permalinks found in tags file:\n${duplicateList}`,
    );
  }
}

export function normalizeTagsFile(data: TagsFileInput): TagsFile {
  return _.mapValues(data, (tag, key) => {
    return {
      label: tag?.label || _.capitalize(key),
      description: tag?.description,
      permalink: tag?.permalink || `/${_.kebabCase(key)}`,
    };
  });
}

type GetTagsFileParams = {
  tags: TagsPluginOptions['tags'];
  contentPaths: ContentPaths;

View on GitHub (pinned to 3f483e80e3)

Solutions

  1. Open the tags.yml reported in the build output and find the permalinks listed under 'Duplicate permalinks found in tags file:' — every listed permalink appears on 2+ entries.
  2. For each duplicate, edit one of the colliding tag entries and assign a distinct explicit permalink (e.g. permalink: /foo-v2).
  3. If the collision is from default-derived permalinks (no explicit permalink field), rename the YAML key itself so its kebab-case slug is unique, or add an explicit permalink to disambiguate.
  4. Re-run the build; ensureUniquePermalinks is called on every tags file load so the error clears as soon as all permalinks are unique.

Example fix

# before (tags.yml)
foo bar:
  label: Foo Bar
foo-bar:
  label: FooBar
# both normalize to permalink /foo-bar => duplicate

# after (tags.yml)
foo bar:
  label: Foo Bar
  permalink: /foo-bar-with-space
foo-bar:
  label: FooBar
Defensive patterns

Strategy: validation

Validate before calling

import _ from 'lodash';

function findDuplicatePermalinks(tags: Record<string, { permalink?: string }>): string[] {
  const seen = new Map<string, number>();
  for (const [key, tag] of Object.entries(tags)) {
    const permalink = tag.permalink ?? `/${_.kebabCase(key)}`;
    seen.set(permalink, (seen.get(permalink) ?? 0) + 1);
  }
  return [...seen.entries()].filter(([, n]) => n > 1).map(([p]) => p);
}

// run before passing tags to getTagsFile
const dupes = findDuplicatePermalinks(myTags);
if (dupes.length) throw new Error(`Fix duplicate permalinks: ${dupes.join(', ')}`);

Type guard

function hasUniquePermalinks(tags: Record<string, { permalink?: string }>): boolean {
  const seen = new Set<string>();
  for (const [key, tag] of Object.entries(tags)) {
    const p = tag.permalink ?? `/${_.kebabCase(key)}`;
    if (seen.has(p)) return false;
    seen.add(p);
  }
  return true;
}

Try / catch

try {
  await getTagsFile({ tags, contentPaths });
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Duplicate permalinks')) {
    // surface the listed permalinks to the user for editing
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling getTagsFile() (or ensureUniquePermalinks() directly) where two tag keys resolve to the same permalink. This happens both when two tags set an identical explicit permalink (e.g. two entries with permalink: /foo) and when defaults collide: normalizeTagsFile() derives a default permalink of `/${_.kebabCase(key)}`, so keys like 'foo bar' and 'foo-bar' both collapse to /foo-bar.

Common situations: Author edits tags.yml and copy-pastes a tag entry forgetting to change its permalink. Two semantically different tag labels whose names kebab-case to the same slug (e.g. 'C++' vs 'cpp', or 'node.js' vs 'nodejs' depending on kebab rules). Migrating from inline front matter tags to a centralized tags.yml and accidentally aliasing several tags to one permalink.

Related errors


AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12). Data as JSON: /api/errors/58c9f00a933e328d. Report an issue: GitHub.