{"record":{"id":"174f4fe1c75878c6","repo":"langflow-ai/langflow","slug":"per-file-metadata-for-filename-r-must-be-a-json","errorCode":null,"errorMessage":"Per-file metadata for {filename!r} must be a JSON object.","messagePattern":"Per-file metadata for (.+?) must be a JSON object\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"error","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":132,"sourceCode":"    try:\n        decoded = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = f\"Per-file metadata is not valid JSON: {exc.msg}\"\n        raise HTTPException(status_code=422, detail=msg) from exc\n    if not isinstance(decoded, dict):\n        msg = \"Per-file metadata must be a JSON object keyed by filename.\"\n        raise HTTPException(status_code=422, detail=msg)\n    if len(decoded) > KB_METADATA_MAX_KEYS:\n        msg = f\"Per-file metadata exceeds the {KB_METADATA_MAX_KEYS} file limit.\"\n        raise HTTPException(status_code=422, detail=msg)\n    out: dict[str, dict[str, Any]] = {}\n    for filename, file_metadata in decoded.items():\n        if not isinstance(filename, str) or not filename:\n            msg = \"Per-file metadata keys must be non-empty filename strings.\"\n            raise HTTPException(status_code=422, detail=msg)\n        if not isinstance(file_metadata, dict):\n            msg = f\"Per-file metadata for {filename!r} must be a JSON object.\"\n            raise HTTPException(status_code=422, detail=msg)\n        out[filename] = validate_user_metadata(file_metadata)\n    return out\n","sourceCodeStart":114,"sourceCodeEnd":135,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L114-L135","documentation":"Raised by validate_file_metadata_map in the knowledge-base upload API when the per-file metadata payload is a JSON object overall, but one of its values (the metadata for a specific filename) is not itself a JSON object. The endpoint validates the file_metadata_map before processing uploads and rejects the whole request with HTTP 422. Each filename key must map to a dict of key/value metadata pairs.","triggerScenarios":"POST to a KB file-upload endpoint with a JSON file_metadata_map whose decoded top-level value is a dict, but where at least one filename entry maps to a string, number, list, or null instead of an object (e.g. {\"report.pdf\": \"category: docs\"} or {\"a.txt\": [\"tag\"]}).","commonSituations":"Clients that send metadata as a flat string, a JSON-encoded string inside a string, or reuse a per-file metadata schema from an older API version that accepted scalars; copy-pasting the single-file metadata object directly as the value instead of nesting it under the filename key.","solutions":["Fix the payload so every filename key maps to a JSON object of metadata pairs: {\"<filename>\": {\"key\": \"value\", ...}}","Verify you are not double-stringifying: if you json.dumps the inner metadata before sending, decode it client-side first","Check the other 422 guards in the same validator (top-level must be an object, <= KB_METADATA_MAX_KEYS entries, non-empty filename keys) if the error persists"],"exampleFix":"// before\nfile_metadata_map = {\"report.pdf\": \"source=legal\"}\n// after\nfile_metadata_map = {\"report.pdf\": {\"source\": \"legal\"}}","handlingStrategy":"validation","validationCode":"def is_valid_file_metadata_map(payload: str | dict) -> bool:\n    import json\n    decoded = json.loads(payload) if isinstance(payload, str) else payload\n    return (\n        isinstance(decoded, dict)\n        and decoded\n        and all(\n            isinstance(k, str) and k and isinstance(v, dict)\n            for k, v in decoded.items()\n        )\n    )","typeGuard":"def is_file_metadata_map(v: object) -> bool:\n    return isinstance(v, dict) and all(\n        isinstance(k, str) and k and isinstance(m, dict) for k, m in v.items()\n    )","tryCatchPattern":"try:\n    resp = client.post(kb_upload_url, files=files, data={\"file_metadata_map\": json.dumps(mm)})\nexcept HTTPStatusError as e:\n    if e.response.status_code == 422:\n        detail = e.response.json()[\"detail\"]\n        if \"must be a JSON object\" in detail:\n            fix_and_retry(mm)  # normalize offending entry to a dict\n        else:\n            raise\n    else:\n        raise","preventionTips":["Always build per-file metadata as {filename: {k: v}} in one place, via a typed client helper","Never json.dumps the inner metadata before nesting it","Add a client-side schema check mirroring the server's: dict-of-dicts, non-empty keys, entry count under the server limit"],"tags":["validation","metadata","knowledge-base","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}