langflow-ai/langflow · warning · HTTPException
Metadata is not valid JSON: {exc.msg}
Error message
Metadata is not valid JSON: {exc.msg} What it means
parse_user_metadata json.loads()es the raw multipart `metadata` form field; if the string is not valid JSON (syntax error), it raises 422 with 'Metadata is not valid JSON: {parser message}' chaining the JSONDecodeError. Empty or missing field is fine (returns {}); this error means a non-empty, syntactically broken payload.
Source
Thrown at src/backend/base/langflow/api/utils/kb_metadata.py:101
"lowercase alphanumeric or underscore characters."
)
raise HTTPException(status_code=422, detail=msg)
if key in KB_METADATA_RESERVED_KEYS:
msg = f"Metadata key '{key}' is reserved for ingestion-internal use."
raise HTTPException(status_code=422, detail=msg)
_validate_value(key, value)
return metadata
def parse_user_metadata(raw: str | None) -> dict[str, Any]:
"""Decode + validate the ``metadata`` form field. Empty/None → ``{}``."""
if not raw:
return {}
try:
decoded = json.loads(raw)
except json.JSONDecodeError as exc:
msg = f"Metadata is not valid JSON: {exc.msg}"
raise HTTPException(status_code=422, detail=msg) from exc
return validate_user_metadata(decoded)
def parse_per_file_metadata(raw: str | None) -> dict[str, dict[str, Any]]:
"""Decode + validate the ``per_file_metadata`` form field.
Shape: ``{filename: {metadata_dict}, ...}``. Each inner dict goes through
the same validator as run-level metadata, so per-file overrides obey the
same key/value rules. Empty/None → ``{}``.
"""
if not raw:
return {}
try:
decoded = json.loads(raw)
except json.JSONDecodeError as exc:
msg = f"Per-file metadata is not valid JSON: {exc.msg}"
raise HTTPException(status_code=422, detail=msg) from exc
if not isinstance(decoded, dict):View on GitHub (pinned to 976ec789d2)
Solutions
- Always build the field with json.dumps(obj) — never f-strings or str(dict) (single quotes are not JSON).
- Read the parser message in the 422 detail: it states exactly what was expected at the failing offset.
- If the field may be empty, omit it or send '' (both map to {}), rather than 'null' text handled elsewhere.
- Test round-trip locally: json.loads(json.dumps(meta)) before sending.
Example fix
# before
form.add_field('metadata', str({'a': 1})) # "{'a': 1}" -> 422
# after
import json
form.add_field('metadata', json.dumps({'a': 1})) Defensive patterns
Strategy: validation
Validate before calling
import json
def safe_metadata_field(obj) -> str:
return json.dumps(obj) if obj else '' # valid JSON or empty Type guard
def is_valid_metadata_json(raw: str) -> bool:
if not raw:
return True
try:
json.loads(raw); return True
except json.JSONDecodeError:
return False Try / catch
try:
parse_user_metadata(raw)
except HTTPException as e:
if e.status_code == 422 and 'not valid JSON' in e.detail:
raise ValueError(f'fix JSON syntax: {e.detail}') from e
raise Prevention
- Never build the field with f-strings or str(dict); always json.dumps.
- Round-trip check locally: json.loads(json.dumps(meta)).
- Use the embedded parser message to locate the exact syntax offset.
When it happens
Trigger: metadata form field set to 'project=alpha' (query-string style), '{project: "alpha"}' (unquoted key), a trailing comma, or truncated JSON from string slicing. json.loads' own message ('Expecting property name enclosed in double quotes', 'Unterminated string starting at...') is embedded, pinpointing the syntax position.
Common situations: Hand-building the field with f-strings or manual concatenation instead of json.dumps; curl --form with missing quotes; content mutated by a gateway or form encoder (unescaped quotes); copied payloads that were pretty-printed then re-wrapped in extra quotes once too many times.
Related errors
- Metadata must be a JSON object.
- Per-file metadata is not valid JSON: {exc.msg}
- Per-file metadata must be a JSON object keyed by filename.
- Metadata value for '{key}' exceeds {KB_METADATA_MAX_VALUE_LE
- Metadata array '{key}' exceeds {KB_METADATA_MAX_ARRAY_LENGTH
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/494d14bf91786b25.
Report an issue: GitHub.