{"record":{"id":"9d36a3af356eb889","repo":"langflow-ai/langflow","slug":"metadata-key-key-r-is-invalid-must-be-1-kb-met","errorCode":null,"errorMessage":"Metadata key {key!r} is invalid: must be 1-{KB_METADATA_MAX_KEY_LENGTH} lowercase alphanumeric or underscore characters.","messagePattern":"Metadata key (.+?) is invalid: must be 1-(.+?) lowercase alphanumeric or underscore characters\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":85,"sourceCode":"    \"\"\"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 = (\n                f\"Metadata key {key!r} is invalid: must be 1-{KB_METADATA_MAX_KEY_LENGTH} \"\n                \"lowercase alphanumeric or underscore characters.\"\n            )\n            raise HTTPException(status_code=422, detail=msg)\n        if key in KB_METADATA_RESERVED_KEYS:\n            msg = f\"Metadata key '{key}' is reserved for ingestion-internal use.\"\n            raise HTTPException(status_code=422, detail=msg)\n        _validate_value(key, value)\n    return metadata\n\n\ndef parse_user_metadata(raw: str | None) -> dict[str, Any]:\n    \"\"\"Decode + validate the ``metadata`` form field. Empty/None → ``{}``.\"\"\"\n    if not raw:\n        return {}\n    try:\n        decoded = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = f\"Metadata is not valid JSON: {exc.msg}\"\n        raise HTTPException(status_code=422, detail=msg) from exc\n    return validate_user_metadata(decoded)\n","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L67-L103","documentation":"Every metadata key must be 1-32 characters (KB_METADATA_MAX_KEY_LENGTH) and consist only of lowercase letters, digits, and underscores — the exact charset in _KEY_ALLOWED_CHARS. The key is also rejected if it is not a string at all. Uppercase letters, hyphens, spaces, dots, and unicode all fail with this 422 message, which interpolates the offending key via repr().","triggerScenarios":"metadata JSON with keys like 'Source-Type', 'file.name', 'category 1', 'ключ', or a 40-char key. Also a non-string JSON key is impossible via json.loads (JSON keys are always strings), so in practice the length/charset predicate is what fails. Applies identically to per-file inner dicts.","commonSituations":"Copying metadata field names from HTTP headers, file-stat structs, or camelCase ORM fields; LLM-generated metadata keys in natural language ('Document Type'); i18n data with non-ASCII keys; namespaced keys like 'x.custom.label'.","solutions":["Normalize keys before submission: lower(), replace non-alphanumerics with '_', trim to 32 chars, collapse repeats.","Adopt a fixed snake_case vocabulary for metadata keys in your ingestion pipeline.","Watch out for a normalization collision ('Doc-Type' and 'doc type' both -> 'doc_type') — dedupe after normalizing.","Add a regex guard on the client: ^[a-z0-9_]{1,32}$ per key."],"exampleFix":"# before\nmetadata = {\"Doc-Type\": \"pdf\", \"file.name\": \"a.pdf\"}  # 422\n\n# after\nimport re\ndef norm_key(k: str) -> str:\n    k2 = re.sub(r'[^a-z0-9_]', '_', k.lower())[:32]\n    return k2\nmetadata = {norm_key(k): v for k, v in raw.items()}","handlingStrategy":"validation","validationCode":"import re\nfrom langflow.utils.kb_constants import KB_METADATA_MAX_KEY_LENGTH as MAXKL\n\ndef normalize_keys(meta: dict) -> dict:\n    out = {}\n    for k, v in meta.items():\n        nk = re.sub(r'[^a-z0-9_]', '_', k.lower())[:MAXKL].strip('_') or 'key'\n        while nk in out:\n            nk = (nk[:MAXKL-2] + f'_{len(out)}')\n        out[nk] = v\n    return out","typeGuard":"KEY_RE = re.compile(r'^[a-z0-9_]{1,32}$')\n\ndef has_valid_keys(meta) -> bool:\n    return all(isinstance(k, str) and KEY_RE.match(k) for k in meta)","tryCatchPattern":"try:\n    validate_user_metadata(meta)\nexcept HTTPException as e:\n    if e.status_code == 422 and 'is invalid: must be' in e.detail:\n        meta = normalize_keys(meta); validate_user_metadata(meta)\n    else:\n        raise","preventionTips":["Standardize on snake_case keys in the whole ingestion pipeline.","Never copy header/camelCase names verbatim into metadata.","Apply KEY_RE client-side before submit; dedupe after normalization."],"tags":["knowledge-base","metadata","validation","naming","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}