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

  1. Flatten nested objects to top-level keys with underscore-joined names ({'extra_a': 1}) respecting the key charset rule (lowercase alphanumeric + underscore).
  2. Drop nulls before submitting: {k: v for k, v in meta.items() if v is not None}.
  3. Represent dicts as a string array of 'k:v' pairs if order matters.
  4. 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

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


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/b8c9a9f4231611e1. Report an issue: GitHub.