{"record":{"id":"b8c9a9f4231611e1","repo":"langflow-ai/langflow","slug":"metadata-value-for-key-must-be-a-string-numbe","errorCode":null,"errorMessage":"Metadata value for '{key}' must be a string, number, bool, or string array; got {type(value).__name__}.","messagePattern":"Metadata value for '(.+?)' must be a string, number, bool, or string array; got (.+?)\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":63,"sourceCode":"    if isinstance(value, str):\n        if len(value) > KB_METADATA_MAX_VALUE_LENGTH:\n            msg = f\"Metadata value for '{key}' exceeds {KB_METADATA_MAX_VALUE_LENGTH} characters.\"\n            raise HTTPException(status_code=422, detail=msg)\n        return\n    if isinstance(value, list):\n        if len(value) > KB_METADATA_MAX_ARRAY_LENGTH:\n            msg = f\"Metadata array '{key}' exceeds {KB_METADATA_MAX_ARRAY_LENGTH} items.\"\n            raise HTTPException(status_code=422, detail=msg)\n        for entry in value:\n            if not isinstance(entry, str):\n                msg = f\"Metadata array '{key}' must contain only strings.\"\n                raise HTTPException(status_code=422, detail=msg)\n            if len(entry) > KB_METADATA_MAX_VALUE_LENGTH:\n                msg = f\"Metadata array entry under '{key}' exceeds {KB_METADATA_MAX_VALUE_LENGTH} characters.\"\n                raise HTTPException(status_code=422, detail=msg)\n        return\n    msg = f\"Metadata value for '{key}' must be a string, number, bool, or string array; got {type(value).__name__}.\"\n    raise HTTPException(status_code=422, detail=msg)\n\n\ndef validate_user_metadata(metadata: dict[str, Any]) -> dict[str, Any]:\n    \"\"\"Enforce the user-metadata contract on a decoded dict.\n\n    Returns the same dict (a shallow copy is *not* made — callers may mutate\n    safely once validation passes). Raises :class:`HTTPException` with a 422\n    status on any violation so FastAPI surfaces an inline error.\n    \"\"\"\n    if not isinstance(metadata, dict):\n        msg = \"Metadata must be a JSON object.\"\n        raise HTTPException(status_code=422, detail=msg)\n    if len(metadata) > KB_METADATA_MAX_KEYS:\n        msg = f\"Metadata exceeds the {KB_METADATA_MAX_KEYS} key limit.\"\n        raise HTTPException(status_code=422, detail=msg)\n    for key, value in metadata.items():\n        if not isinstance(key, str) or not _is_valid_key(key):\n            msg = (","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L45-L81","documentation":"The catch-all type rejection in _validate_value: a metadata value that is not a bool, int, float, str, or list of strings (e.g. dict, None, tuple-like JSON nested object) is rejected with 422 and the message names the offending Python type. The metadata contract is intentionally flat and primitive so vector-store filters and the chunks browser can rely on it.","triggerScenarios":"metadata JSON contains a nested object ({\"extra\": {\"a\": 1}}) or an explicit null ({\"note\": null}); since json.loads maps null to None, None hits this branch. Also tuples are impossible in JSON but dicts nested one level deep are the classic case. Works identically inside per_file_metadata inner dicts.","commonSituations":"Piping arbitrary JSON config into the metadata field; omitting a value resulting in null; LLM tool output with nested structure; developers assuming metadata is schemaless like Mongo or object metadata in S3.","solutions":["Flatten nested objects to top-level keys with underscore-joined names ({'extra_a': 1}) respecting the key charset rule (lowercase alphanumeric + underscore).","Drop nulls before submitting: {k: v for k, v in meta.items() if v is not None}.","Represent dicts as a string array of 'k:v' pairs if order matters.","Validate the whole payload with the same allowlist logic client-side before the POST."],"exampleFix":"# before\nmetadata = {\"source_info\": {\"url\": u, \"ts\": 1}, \"note\": None}  # 422\n\n# after\nmetadata = {\"source_url\": u, \"source_ts\": 1}  # flattened, null dropped","handlingStrategy":"type-guard","validationCode":"def flatten_and_drop_nulls(meta: dict) -> dict:\n    out = {}\n    for k, v in meta.items():\n        if v is None:\n            continue\n        if isinstance(v, dict):\n            for k2, v2 in v.items():\n                out[f'{k}_{k2}'] = v2  # keys still must match ^[a-z0-9_]{1,32}$\n        else:\n            out[k] = v\n    return out","typeGuard":"def is_valid_meta_value(v) -> bool:\n    if isinstance(v, (bool, int, float, str)):\n        return len(v) <= 256 if isinstance(v, str) else True\n    return isinstance(v, list) and len(v) <= 16 and all(isinstance(e, str) and len(e) <= 256 for e in v)","tryCatchPattern":"try:\n    validate_user_metadata(meta)\nexcept HTTPException as e:\n    if e.status_code == 422 and 'must be a string, number, bool' in e.detail:\n        meta = flatten_and_drop_nulls(meta); validate_user_metadata(meta)\n    else:\n        raise","preventionTips":["Treat KB metadata as flat key/value, not schemaless JSON.","Drop nulls before serializing: {k: v for k, v in meta.items() if v is not None}.","Run the type guard above over every value before the POST."],"tags":["knowledge-base","metadata","validation","type-error","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}