{"record":{"id":"186ed40e815139ee","repo":"laurent22/joplin","slug":"manifest-plugin-id-is-not-set-in-manifestpath","errorCode":null,"errorMessage":"Manifest plugin ID is not set in ${manifestPath}","messagePattern":"Manifest plugin ID is not set in (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/generator-joplin/generators/app/templates/webpack.config.js","lineNumber":125,"sourceCode":"\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\n\t\t// Max file size is 1MB\n\t\tconst fileMaxSize = 1024;\n\t\tconst fileSize = fs.statSync(screenshotPath).size / 1024;\n\t\tif (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);\n\t}\n}\n\nfunction readManifest(manifestPath) {\n\tconst content = fs.readFileSync(manifestPath, 'utf8');\n\tconst output = JSON.parse(content);\n\tif (!output.id) throw new Error(`Manifest plugin ID is not set in ${manifestPath}`);\n\tvalidateCategories(output.categories);\n\tvalidateScreenshots(output.screenshots);\n\treturn output;\n}\n\nfunction createPluginArchive(sourceDir, destPath) {\n\tconst distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true, windowsPathsNoEscape: true })\n\t\t.map(f => f.substr(sourceDir.length + 1));\n\n\tif (!distFiles.length) throw new Error('Plugin archive was not created because the \"dist\" directory is empty');\n\tfs.removeSync(destPath);\n\n\ttar.create(\n\t\t{\n\t\t\tstrict: true,\n\t\t\tportable: true,\n\t\t\tfile: destPath,\n\t\t\tcwd: sourceDir,","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/generator-joplin/generators/app/templates/webpack.config.js#L107-L143","documentation":"Thrown by readManifest() right after JSON.parse of manifest.json. The plugin id is the unique identifier used by Joplin to register and update the plugin; it cannot be empty. The check is `if (!output.id)`, so missing, empty-string, or null all trigger it.","triggerScenarios":"manifest.json parses successfully but has no `id` field, or `id` is set to \"\" or null.","commonSituations":"New plugin scaffold where the id was not filled in; field accidentally deleted during a manual edit; key renamed to `pluginId` or `name`.","solutions":["Open manifest.json and add a unique `id` (typically a reverse-DNS or slug-style identifier, e.g. \"com.example.my-plugin\").","Ensure the id is unique across the Joplin plugin repository to avoid collisions.","Re-run the build."],"exampleFix":"// before (manifest.json)\n{ \"version\": \"1.0.0\", \"app_min_version\": \"2.6\" }\n// after\n{ \"id\": \"com.example.my-plugin\", \"version\": \"1.0.0\", \"app_min_version\": \"2.6\" }","handlingStrategy":"validation","validationCode":"const manifest = require('./manifest.json');\nif (!manifest.id || typeof manifest.id !== 'string') {\n  throw new Error('manifest.json must define a non-empty string `id`');\n}","typeGuard":"const hasId = (m) => !!m && typeof m.id === 'string' && m.id.length > 0;","tryCatchPattern":"try { readManifest(manifestPath); }\ncatch (e) { if (/plugin ID is not set/.test(e.message)) { /* prompt for an id */ } else throw e; }","preventionTips":["Enforce a JSON Schema requiring a non-empty `id` on manifest.json in CI.","Never delete the id field when editing manifest.json; keep it under version control and review diffs."],"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"}