{"record":{"id":"c8dccbe738e4071e","repo":"paperclipai/paperclip","slug":"configuration-does-not-match-the-plugin-s-instance","errorCode":null,"errorMessage":"Configuration does not match the plugin's instanceConfigSchema","messagePattern":"Configuration does not match the plugin's instanceConfigSchema","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/src/routes/plugins.ts","lineNumber":2340,"sourceCode":"    }\n\n    // Strip devUiUrl unless the caller is an instance admin. devUiUrl activates\n    // a dev-proxy in the static file route that could be abused for SSRF if any\n    // board-level user were allowed to set it.\n    if (\n      \"devUiUrl\" in body.configJson &&\n      !(req.actor.type === \"board\" && req.actor.isInstanceAdmin)\n    ) {\n      delete body.configJson.devUiUrl;\n    }\n\n    // Validate configJson against the plugin's instanceConfigSchema (if declared).\n    // This ensures CLI/API callers get the same validation the UI performs client-side.\n    const schema = plugin.manifestJson?.instanceConfigSchema;\n    if (schema && Object.keys(schema).length > 0) {\n      const validation = validateInstanceConfig(body.configJson, schema);\n      if (!validation.valid) {\n        res.status(400).json({\n          error: \"Configuration does not match the plugin's instanceConfigSchema\",\n          fieldErrors: validation.errors,\n        });\n        return;\n      }\n    }\n\n    try {\n      const secretRefs = extractSecretRefBindingsFromConfig(body.configJson, schema);\n      await validatePluginSecretRefsForCompany(companyId, secretRefs);\n      await secretService(db).syncSecretRefsForTarget(\n        companyId,\n        { targetType: \"plugin\", targetId: plugin.id },\n        secretRefs,\n        { replaceAll: true },\n      );\n\n      const result = await registry.upsertConfig(plugin.id, companyId, {","sourceCodeStart":2322,"sourceCodeEnd":2358,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L2322-L2358","documentation":"Returned as HTTP 400 by POST /api/plugins/:pluginId/config when configJson fails server-side validation against the plugin's declared instanceConfigSchema (from the plugin manifest). The response includes a fieldErrors array describing exactly which fields are wrong, so CLI/API callers get the same validation the UI performs client-side.","triggerScenarios":"Sending configJson that misses a required field defined in the plugin's instanceConfigSchema, supplies a wrong primitive type (string where number is declared), includes an unknown/extra property when the schema does not allow it, or omits nested required keys.","commonSituations":"Plugin upgraded its manifest and now requires new fields; the caller copied an example config from an older plugin version; a typo in a field name (api_key vs apiKey) makes it look like a missing required field.","solutions":["Read the fieldErrors array in the 400 response — it names the exact failing fields and reasons","Fetch the plugin manifest (GET /api/plugins/:pluginId) and inspect instanceConfigSchema to see required fields, types, and enums","Fix the config values to match the schema and resend","If the schema itself is wrong, fix the plugin's manifest instanceConfigSchema and reinstall/register the plugin"],"exampleFix":"// before\nPOST /api/plugins/scheduler/config\n{ \"companyId\": \"c1\", \"configJson\": { \"region\": \"eu\" } }\n// fieldErrors: [ { path: 'port', message: 'Required' } ]\n// after\n{ \"companyId\": \"c1\", \"configJson\": { \"region\": \"eu\", \"port\": 8080 } }","handlingStrategy":"validation","validationCode":"const plugin = await (await fetch(`/api/plugins/${pluginId}`)).json();\nconst schema = plugin.manifestJson?.instanceConfigSchema;\nif (schema) {\n  for (const key of Object.keys(schema)) {\n    if (schema[key]?.required && configJson[key] === undefined) {\n      throw new Error(`Missing required config field: ${key}`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try { await savePluginConfig(pluginId, companyId, configJson); } catch (e) { if (e.status === 400 && e.body.fieldErrors) showFieldErrors(e.body.fieldErrors); else throw e; }","preventionTips":["Validate against the plugin's instanceConfigSchema before saving — fetch it from the plugin record","Surface fieldErrors from the 400 response directly in forms instead of a generic message","Re-sync config templates whenever a plugin updates its manifest schema"],"tags":["http-400","schema-validation","plugins","config","field-errors"],"backgroundTag":"schema-validation-failed","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}