{"record":{"id":"f41713291b7d71ef","repo":"NginxProxyManager/nginx-proxy-manager","slug":"plugin-not-found","errorCode":null,"errorMessage":"Plugin not found","messagePattern":"Plugin not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"backend/routes/ci.js","lineNumber":45,"sourceCode":"\t\tres.status(200).send(dnsPlugins);\n\t});\n\n/**\n * /api/ci/certbot-plugins/{plugin}\n */\nrouter\n\t.route(\"/certbot-plugins/:plugin\")\n\t.options((_, res) => {\n\t\tres.sendStatus(204);\n\t})\n\n\t// Install a certbot plugin\n\t.post(async (req, res, next) => {\n\t\ttry {\n\t\t\tconst pluginName = req.params.plugin;\n\t\t\t// check if plugin exists\n\t\t\tif (!dnsPlugins[pluginName]) {\n\t\t\t\treturn res.status(404).send({\n\t\t\t\t\terror: \"Plugin not found\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait installPlugin(pluginName);\n\t\t\tres.status(200).send(true);\n\t\t} catch (err) {\n\t\t\tdebug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);\n\t\t\tnext(err);\n\t\t}\n\t\treturn;\n\t});\n\nexport default router;\n","sourceCodeStart":27,"sourceCodeEnd":60,"githubUrl":"https://github.com/NginxProxyManager/nginx-proxy-manager/blob/934a3fafe5ae82d752f0a18c0f1d0eb050296730/backend/routes/ci.js#L27-L60","documentation":"This is an HTTP 404 from POST /api/nginx/certificates/:plugin (the certbot DNS plugin install endpoint) in backend/routes/ci.js. It compares req.params.plugin against the dnsPlugins registry; an unknown or misspelled name yields a 404 with error \"Plugin not found\" instead of attempting a package install.","triggerScenarios":"POSTing to the endpoint with a plugin slug that is not a key of the dnsPlugins map — e.g. 'cloudflaree', 'route53' when unsupported, or passing a display name like 'Cloudflare DNS' instead of the slug 'cloudflare'.","commonSituations":"Typos in the plugin slug from hand-crafted curl/API scripts, UI dropdowns sending labels instead of keys, or scripts written against an older/newer version of NPM whose dnsPlugins registry differs (provider added/removed in that version).","solutions":["Check the exact key in backend/internal/dns-plugins.js (the dnsPlugins object) and use that slug verbatim, e.g. 'cloudflare', 'digitalocean', 'google'","List available plugins first (the GET side of the same route / docs) and pick the key, not the display name","If the provider genuinely is missing, upgrade NPM to a version that includes the dns-plugins entry, or add a custom entry locally and rebuild"],"exampleFix":"# before\ncurl -X POST https://npm.example.com/api/nginx/certificates/Cloudflare\n\n# after\ncurl -X POST https://npm.example.com/api/nginx/certificates/cloudflare","handlingStrategy":"validation","validationCode":"import dnsPlugins from './internal/dns-plugins.js';\n\nconst pluginExists = (name: string) => Object.prototype.hasOwnProperty.call(dnsPlugins, name);\n\nif (!pluginExists(pluginName)) {\n  throw new Error(`Unsupported plugin '${pluginName}'. Available: ${Object.keys(dnsPlugins).join(', ')}`);\n}","typeGuard":"const isKnownDnsPlugin = (name: string): name is keyof typeof dnsPlugins =>\n  Object.prototype.hasOwnProperty.call(dnsPlugins, name);","tryCatchPattern":"const res = await fetch(`/api/nginx/certificates/${plugin}`);\nif (res.status === 404) {\n  // re-fetch the plugin list and reconcile the slug before retrying\n}","preventionTips":["Drive plugin selection from the server-provided list (GET endpoint) so slugs always match the deployed version","Pin API scripts to a known NPM version and re-verify plugin keys after upgrades","Add integration tests asserting every slug offered by the UI exists in dnsPlugins"],"tags":["nginx-proxy-manager","dns-challenge","certbot","http-404","api-validation"],"backgroundTag":"resource-not-found","analyzedSha":"934a3fafe5ae82d752f0a18c0f1d0eb050296730","analyzedAt":"2026-08-27T14:34:22.258Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}