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
- 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.
- For each duplicate, edit one of the colliding tag entries and assign a distinct explicit permalink (e.g. permalink: /foo-v2).
- 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.
- 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
- Always assign explicit permalink values in tags.yml instead of relying on kebab-case defaults for tag keys that could collide.
- Add a pre-commit lint script that loads tags.yml and runs ensureUniquePermalinks so collisions never reach the build.
- Treat permalink values as you would slugs: lowercase, hyphenated, and globally unique within the file.
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
- There was an error extracting tags from file: ${tagsFileInpu
- The following permalinks are duplicated: ${errorMessage}
- Author socials should be usernames/userIds/handles, or fully
- Invalid route config: path must be a string and component is
- Route ${route.path} has conflicting props declared using bot
AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12).
Data as JSON: /api/errors/58c9f00a933e328d.
Report an issue: GitHub.