laurent22/joplin · error · Error

Repeated categories are not allowed

Error message

Repeated categories are not allowed

What it means

Thrown by validateCategories (in the plugin template's webpack.config.js) when the manifest.json categories array contains duplicate entries — i.e. categories.length !== new Set(categories).size. Categories classify the plugin on the Joplin plugin registry and duplicates are rejected to keep the listing clean. Validation runs at build time when readManifest parses src/manifest.json.

Source

Thrown at packages/generator-joplin/generators/app/templates/webpack.config.js:93

}

function currentGitInfo() {
	try {
		let branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
		const commit = execSync('git rev-parse HEAD', { stdio: 'pipe' }).toString().trim();
		if (branch === 'HEAD') branch = 'master';
		return `${branch}:${commit}`;
	} catch (error) {
		const messages = error.message ? error.message.split('\n') : [''];
		console.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));
		console.info(chalk.cyan('Git information will not be stored in plugin info file'));
		return '';
	}
}

function validateCategories(categories) {
	if (!categories) return null;
	if ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');
	// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
	categories.forEach(category => {
		if (!allPossibleCategories.map(category => { return category.name; }).includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are: \n${allPossibleCategories.map(category => { return category.name; })}\n`);
	});
}

function validateScreenshots(screenshots) {
	if (!screenshots) return null;
	for (const screenshot of screenshots) {
		if (!screenshot.src) throw new Error('You must specify a src for each screenshot');

		// Avoid attempting to download and verify URL screenshots.
		if (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {
			continue;
		}

		const screenshotType = screenshot.src.split('.').pop();
		if (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \n${allPossibleScreenshotsType}\n`);

View on GitHub (pinned to 2654b33620)

Solutions

  1. Open src/manifest.json and remove duplicate entries from the categories array.
  2. Use a JSON-aware editor or lint the manifest to detect dupes before building.
  3. De-duplicate programmatically: [...new Set(manifest.categories)].

Example fix

// before — src/manifest.json
"categories": ["editor", "productivity", "editor"]
// after
"categories": ["editor", "productivity"]
Defensive patterns

Strategy: validation

Validate before calling

const manifest = JSON.parse(fs.readFileSync('src/manifest.json', 'utf8'));
if (manifest.categories) {
  const set = new Set(manifest.categories);
  if (set.size !== manifest.categories.length) {
    throw new Error('Duplicate categories in manifest.json');
  }
}

Type guard

function hasUniqueCategories(cats) {
  return Array.isArray(cats) && new Set(cats).size === cats.length;
}

Prevention

When it happens

Trigger: manifest.json has "categories": ["editor", "editor"] or ["productivity", "productivity", "tags"]; a copy-paste when editing the manifest duplicated an entry; a script regenerated the manifest and inserted a category twice.

Common situations: Plugin author hand-edited manifest.json and duplicated a category; a merge conflict resolution left a doubled entry; a scaffolding/tooling bug wrote the same category twice.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/1aa00a43114eebe7. Report an issue: GitHub.