laurent22/joplin · error · Error
is not a valid category. Please make sure that the category…
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.
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
- Manifest plugin ID is not set in
- Max screenshot file size is
- is not a valid screenshot type. Valid types are
- You must specify a src for each screenshot
- A config file must be specified via the…
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/96aafe7b80fddf4f.
Report an issue: GitHub.
Appendix: 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)