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

  1. Send configJson as a real JSON object in the request body, e.g. {"companyId":"c1","configJson":{"apiKey":"..."}}
  2. Set the Content-Type: application/json header so Express JSON middleware parses the body
  3. If the config arrives as a string in your code, JSON.parse it before putting it into the request body
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18). Data as JSON: /api/errors/488a57b18bf58ec9. Report an issue: GitHub.