{"record":{"id":"96aafe7b80fddf4f","repo":"laurent22/joplin","slug":"category-is-not-a-valid-category-please-make-s","errorCode":null,"errorMessage":"${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","messagePattern":"(.+?) is not a valid category\\. Please make sure that the category name is lowercase\\. Valid categories are: \n(.+?)\\)\\}\n","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/generator-joplin/generators/app/templates/webpack.config.js","lineNumber":96,"sourceCode":"\ttry {\n\t\tlet branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();\n\t\tconst commit = execSync('git rev-parse HEAD', { stdio: 'pipe' }).toString().trim();\n\t\tif (branch === 'HEAD') branch = 'master';\n\t\treturn `${branch}:${commit}`;\n\t} catch (error) {\n\t\tconst messages = error.message ? error.message.split('\\n') : [''];\n\t\tconsole.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));\n\t\tconsole.info(chalk.cyan('Git information will not be stored in plugin info file'));\n\t\treturn '';\n\t}\n}\n\nfunction validateCategories(categories) {\n\tif (!categories) return null;\n\tif ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');\n\t// eslint-disable-next-line github/array-foreach -- Old code before rule was applied\n\tcategories.forEach(category => {\n\t\tif (!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`);\n\t});\n}\n\nfunction validateScreenshots(screenshots) {\n\tif (!screenshots) return null;\n\tfor (const screenshot of screenshots) {\n\t\tif (!screenshot.src) throw new Error('You must specify a src for each screenshot');\n\n\t\t// Avoid attempting to download and verify URL screenshots.\n\t\tif (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst screenshotType = screenshot.src.split('.').pop();\n\t\tif (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \\n${allPossibleScreenshotsType}\\n`);\n\n\t\tconst screenshotPath = path.resolve(rootDir, screenshot.src);\n","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/generator-joplin/generators/app/templates/webpack.config.js#L78-L114","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","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."],"exampleFix":"// before (manifest.json)\n\"categories\": [\"Productivity\", \"Dev-Tools\"]\n// after\n\"categories\": [\"productivity\", \"developer tools\"]","handlingStrategy":"validation","validationCode":"// Validate manifest categories against the generator's known list before building.\nconst manifest = require('./manifest.json');\nconst known = allPossibleCategories.map(c => c.name); // from generator-joplin exports\nconst bad = (manifest.categories || []).filter(c => !known.includes(c));\nif (bad.length) throw new Error(`Invalid categories (lowercase required): ${bad.join(', ')}`);","typeGuard":"const isKnownCategory = (c, known) => typeof c === 'string' && c === c.toLowerCase() && known.includes(c);","tryCatchPattern":"try {\n  validateCategories(manifest.categories);\n} catch (e) {\n  if (/is not a valid category/.test(e.message)) { /* surface to author, list valid names */ }\n  else throw e;\n}","preventionTips":["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."],"tags":["plugin-build","manifest-validation","generator-joplin"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}