laurent22/joplin · error · Error
${category} is not a valid category. Please make sure that t
Error message
${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are:
${allPossibleCategories.map(category => { return category.name; })}
What it means
Thrown by validateCategories() during plugin build, after readManifest() parses manifest.json. Each entry in the manifest's `categories` array is checked against allPossibleCategories (by mapping each to its `.name`). The error fires when a category string does not match any known category name. The error message interpolates the list of valid names so the developer can see exactly what is accepted.
Source
Thrown at packages/generator-joplin/generators/app/templates/webpack.config.js:96
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`);
const screenshotPath = path.resolve(rootDir, screenshot.src);
View on GitHub (pinned to 2654b33620)
Solutions
- Open manifest.json and lowercase every entry in the `categories` array (the message explicitly asks for lowercase).
- Compare each category against the list printed in the error message (it is the .name of each allPossibleCategories entry) and correct mismatches.
- If the category legitimately should exist, update packages/generator-joplin (yarn upgrade generator-joplin) so allPossibleCategories includes it.
- Re-run the build (npm run dist / yarn dist) to confirm validation passes.
Example fix
// before (manifest.json) "categories": ["Productivity", "Dev-Tools"] // after "categories": ["productivity", "developer tools"]
Defensive patterns
Strategy: validation
Validate before calling
// Validate manifest categories against the generator's known list before building.
const manifest = require('./manifest.json');
const known = allPossibleCategories.map(c => c.name); // from generator-joplin exports
const bad = (manifest.categories || []).filter(c => !known.includes(c));
if (bad.length) throw new Error(`Invalid categories (lowercase required): ${bad.join(', ')}`); Type guard
const isKnownCategory = (c, known) => typeof c === 'string' && c === c.toLowerCase() && known.includes(c);
Try / catch
try {
validateCategories(manifest.categories);
} catch (e) {
if (/is not a valid category/.test(e.message)) { /* surface to author, list valid names */ }
else throw e;
} Prevention
- Treat manifest.json categories as a controlled vocabulary — copy from the error's printed list, do not freehand.
- Always lowercase category strings in any manifest-generating code.
- Add a pre-build lint script that re-validates manifest.json against the generator's exported category list.
When it happens
Trigger: readManifest() -> validateCategories(output.categories) -> categories.forEach: a category string is not in allPossibleCategories.map(c => c.name). Most commonly a capital-letter category (e.g. 'Productivity') or a typo ('dev-tools' vs 'developer tools').
Common situations: Author hand-edits manifest.json and uses Title Case categories; copies a category name from docs that has since been renamed; uses an outdated generator-joplin template whose allPossibleCategories list does not yet include a newly added category.
Related errors
- You must specify a src for each screenshot
- ${screenshotType} is not a valid screenshot type. Valid type
- Max screenshot file size is ${fileMaxSize}KB. ${screenshotPa
- Manifest plugin ID is not set in ${manifestPath}
- Plugin archive was not created because the "dist" directory
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/96aafe7b80fddf4f.
Report an issue: GitHub.