langflow-ai/langflow · warning · HTTPException
Metadata value for '{key}' must be a string, number, bool, o
Error message
Metadata value for '{key}' must be a string, number, bool, or string array; got {type(value).__name__}. What it means
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.
Source
Thrown at src/backend/base/langflow/api/utils/kb_metadata.py:63
if isinstance(value, str):
if len(value) > KB_METADATA_MAX_VALUE_LENGTH:
msg = f"Metadata value for '{key}' exceeds {KB_METADATA_MAX_VALUE_LENGTH} characters."
raise HTTPException(status_code=422, detail=msg)
return
if isinstance(value, list):
if len(value) > KB_METADATA_MAX_ARRAY_LENGTH:
msg = f"Metadata array '{key}' exceeds {KB_METADATA_MAX_ARRAY_LENGTH} items."
raise HTTPException(status_code=422, detail=msg)
for entry in value:
if not isinstance(entry, str):
msg = f"Metadata array '{key}' must contain only strings."
raise HTTPException(status_code=422, detail=msg)
if len(entry) > KB_METADATA_MAX_VALUE_LENGTH:
msg = f"Metadata array entry under '{key}' exceeds {KB_METADATA_MAX_VALUE_LENGTH} characters."
raise HTTPException(status_code=422, detail=msg)
return
msg = f"Metadata value for '{key}' must be a string, number, bool, or string array; got {type(value).__name__}."
raise HTTPException(status_code=422, detail=msg)
def validate_user_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
"""Enforce the user-metadata contract on a decoded dict.
Returns the same dict (a shallow copy is *not* made — callers may mutate
safely once validation passes). Raises :class:`HTTPException` with a 422
status on any violation so FastAPI surfaces an inline error.
"""
if not isinstance(metadata, dict):
msg = "Metadata must be a JSON object."
raise HTTPException(status_code=422, detail=msg)
if len(metadata) > KB_METADATA_MAX_KEYS:
msg = f"Metadata exceeds the {KB_METADATA_MAX_KEYS} key limit."
raise HTTPException(status_code=422, detail=msg)
for key, value in metadata.items():
if not isinstance(key, str) or not _is_valid_key(key):
msg = (View on GitHub (pinned to 976ec789d2)
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.
Example fix
# before
metadata = {"source_info": {"url": u, "ts": 1}, "note": None} # 422
# after
metadata = {"source_url": u, "source_ts": 1} # flattened, null dropped Defensive patterns
Strategy: type-guard
Validate before calling
def flatten_and_drop_nulls(meta: dict) -> dict:
out = {}
for k, v in meta.items():
if v is None:
continue
if isinstance(v, dict):
for k2, v2 in v.items():
out[f'{k}_{k2}'] = v2 # keys still must match ^[a-z0-9_]{1,32}$
else:
out[k] = v
return out Type guard
def is_valid_meta_value(v) -> bool:
if isinstance(v, (bool, int, float, str)):
return len(v) <= 256 if isinstance(v, str) else True
return isinstance(v, list) and len(v) <= 16 and all(isinstance(e, str) and len(e) <= 256 for e in v) Try / catch
try:
validate_user_metadata(meta)
except HTTPException as e:
if e.status_code == 422 and 'must be a string, number, bool' in e.detail:
meta = flatten_and_drop_nulls(meta); validate_user_metadata(meta)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Metadata array '{key}' must contain only strings.
- Metadata value for '{key}' exceeds {KB_METADATA_MAX_VALUE_LE
- Metadata array '{key}' exceeds {KB_METADATA_MAX_ARRAY_LENGTH
- Metadata array entry under '{key}' exceeds {KB_METADATA_MAX_
- Metadata must be a JSON object.
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/b8c9a9f4231611e1.
Report an issue: GitHub.