paperclipai/paperclip · error
"configJson" is required and must be an object
Error message
"configJson" is required and must be an object
What it means
Returned as HTTP 400 by POST /api/plugins/:pluginId/config when the request body has no configJson or configJson is not a plain object. The route (server/src/routes/plugins.ts:2298) explicitly rejects null, arrays, strings, and numbers; only a JSON object is accepted. Note the companyId check (requirePluginConfigCompanyId) runs first, so hitting this error means companyId already passed.
Source
Thrown at server/src/routes/plugins.ts:2320
* Errors:
* - 400 if request validation fails
* - 404 if plugin not found
*/
router.post("/plugins/:pluginId/config", async (req, res) => {
assertInstanceAdmin(req);
assertPluginManagementVisible();
const { pluginId } = req.params;
const plugin = await resolvePlugin(registry, pluginId);
if (!plugin) {
res.status(404).json({ error: "Plugin not found" });
return;
}
const body = req.body as { companyId?: unknown; configJson?: Record<string, unknown> } | undefined;
const companyId = requirePluginConfigCompanyId(req, body?.companyId);
if (!body?.configJson || typeof body.configJson !== "object" || Array.isArray(body.configJson)) {
res.status(400).json({ error: '"configJson" is required and must be an object' });
return;
}
// Strip devUiUrl unless the caller is an instance admin. devUiUrl activates
// a dev-proxy in the static file route that could be abused for SSRF if any
// board-level user were allowed to set it.
if (
"devUiUrl" in body.configJson &&
!(req.actor.type === "board" && req.actor.isInstanceAdmin)
) {
delete body.configJson.devUiUrl;
}
// Validate configJson against the plugin's instanceConfigSchema (if declared).
// This ensures CLI/API callers get the same validation the UI performs client-side.
const schema = plugin.manifestJson?.instanceConfigSchema;
if (schema && Object.keys(schema).length > 0) {
const validation = validateInstanceConfig(body.configJson, schema);View on GitHub (pinned to a7e689b3c3)
Solutions
- Send configJson as a real JSON object in the request body, e.g. {"companyId":"c1","configJson":{"apiKey":"..."}}
- Set the Content-Type: application/json header so Express JSON middleware parses the body
- If the config arrives as a string in your code, JSON.parse it before putting it into the request body
- Confirm you are not sending an array or null; wrap values in an object ({items: [...]}, not [...])
Example fix
// before
await fetch(`/api/plugins/${pluginId}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ companyId, configJson: JSON.stringify(cfg) }),
});
// after
await fetch(`/api/plugins/${pluginId}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ companyId, configJson: cfg }),
}); Defensive patterns
Strategy: validation
Validate before calling
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
// before POST /api/plugins/:id/config
if (!isPlainObject(configJson)) throw new Error('configJson must be a plain object');
await fetch(`/api/plugins/${pluginId}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ companyId, configJson }),
}); Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try { await savePluginConfig(...); } catch (e) { if (e.status === 400 && /configJson/.test(e.body.error)) fixBodyShape(); else throw e; } Prevention
- Always construct request bodies with JSON.stringify of a plain object, never pre-stringified nested values
- Default to Content-Type: application/json on every plugin API call
- Keep a typed PluginConfigPayload interface so TypeScript rejects stringified configs at compile time
When it happens
Trigger: Calling POST /api/plugins/:pluginId/config with body {companyId: 'c1'} (configJson omitted), configJson: null, configJson: '[1,2]' (array), or a double-stringified body like configJson: '{"k":1}'. Also triggered when the request lacks Content-Type: application/json so Express never parsed the body and req.body is undefined.
Common situations: Stringifying the config twice (JSON.stringify of an already-stringified value), sending the array form-data variant, forgetting the field in a curl/CLI call, or using a client that defaults to text/plain so req.body stays undefined.
Related errors
- Configuration does not match the plugin's instanceConfigSche
- Invalid file path
- devUiUrl must use http or https protocol
- devUiUrl must target localhost
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18).
Data as JSON: /api/errors/488a57b18bf58ec9.
Report an issue: GitHub.