{"record":{"id":"d9d9d81553b28242","repo":"payloadcms/payload","slug":"value-is-not-allowed-as-a-json-query-value","errorCode":null,"errorMessage":"${value} is not allowed as a JSON query value","messagePattern":"(.+?) is not allowed as a JSON query value","errorType":"validation","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/drizzle/src/postgres/createJSONQuery/index.ts","lineNumber":32,"sourceCode":"  not_in: 'in',\n  not_like: '!like_regex',\n}\n\nconst sanitizeValue = (value: unknown, operator?: string): string => {\n  if (value === null) {\n    return `NULL`\n  }\n\n  if (typeof value === 'number' || typeof value === 'boolean') {\n    return `${value}`\n  }\n\n  if (typeof value !== 'string') {\n    throw new Error('Invalid value type')\n  }\n\n  if (!SAFE_STRING_REGEX.test(value)) {\n    throw new APIError(`${value} is not allowed as a JSON query value`, 400)\n  }\n\n  const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n\n  const prefix = ['like', 'not_like'].includes(operator ?? '') ? '(?i)' : ''\n\n  return `\"${prefix}${escaped}\"`\n}\n\nexport const createJSONQuery = ({ column, operator, pathSegments, value }: CreateJSONQueryArgs) => {\n  const columnName = typeof column === 'object' ? column.name : column\n  const jsonPaths = pathSegments\n    .slice(1)\n    .map((key) => {\n      return `${sanitizePathSegment(key)}[*]`\n    })\n    .join('.')\n","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/drizzle/src/postgres/createJSONQuery/index.ts#L14-L50","documentation":"A 400 `APIError` from `sanitizeValue` inside `createJSONQuery`: a query value used against a JSON/JSONB column (arrays, `json`, `richText`, localized JSON paths) failed the strict `SAFE_STRING_REGEX` (`/^[\\w @.\\-+:]*$/`). The regex is an allow-list that prevents SQL/JSONPath injection when interpolating values into raw `jsonb_path_exists(...)` SQL, so anything outside word chars, space, `@ . - + :` is rejected.","triggerScenarios":"Querying an array/json field with `contains`/`like`/`equals`/`in` and a value containing punctuation or symbols not in the allow-list — e.g. quotes, parentheses, commas, `<`, `&`, emoji, or accented/non-ASCII letters. Triggers through the Payload REST/local API `where` query on array/JSON fields when `pathSegments.length > 1` (nested JSON path).","commonSituations":"Free-text search boxes feeding `contains` against an array/json field with user input containing punctuation; querying richText; localized JSON field queries with special characters; data containing accented characters that the ASCII-only word class rejects.","solutions":["Sanitize/strip user input to safe characters before sending it as a `where` query value against array/json fields.","For free-text search, prefer a dedicated text/textarea field (parameterized `like`) instead of querying raw JSON.","If the value legitimately contains punctuation, perform the filter in application code after fetching, or store the searchable value in a normal column.","If you need accent-insensitive matching on text, use the `unaccent` operator handler against a text column rather than a JSON path."],"exampleFix":"// before: payload.find({ collection: 'pages', where: { 'meta.tags': { contains: 'C++ (API)' } } })\n//  -> throws '<value> is not allowed as a JSON query value'\n// after: strip unsafe characters, or move tags to a relationship/text field\nconst safe = userInput.replace(/[^\\w @.\\-+:]/g, '')\nawait payload.find({ collection: 'pages', where: { 'meta.tags': { contains: safe } } })","handlingStrategy":"validation","validationCode":"// Validate JSON-query values against the same allow-list before sending\nconst SAFE = /^[\\w @.\\-+:]*$/\nfunction safeJsonValue(v) {\n  if (v === null || typeof v === 'number' || typeof v === 'boolean') return v\n  if (typeof v === 'string') {\n    if (!SAFE.test(v)) throw new Error(`Unsafe JSON query value: ${v}`)\n    return v\n  }\n  throw new Error('Invalid JSON query value type')\n}\nconst clean = safeJsonValue(userInput)\nawait payload.find({ collection, where: { 'meta.tags': { contains: clean } } })","typeGuard":"const isSafeJsonQueryString = (v: unknown): v is string =>\n  typeof v === 'string' && /^[\\w @.\\-+:]*$/.test(v)","tryCatchPattern":"try {\n  await payload.find({ collection, where: { 'meta.tags': { contains: q } } })\n} catch (err) {\n  if (err?.statusCode === 400 && /not allowed as a JSON query value/.test(err.message)) {\n    return res.status(400).json({ error: 'Search contains unsupported characters.' })\n  }\n  throw err\n}","preventionTips":["Sanitize free-text input before using it in array/json where clauses.","Prefer searchable text columns over raw JSON for user-facing search.","Document the allow-list character set for API consumers querying JSON fields."],"tags":["query","json","validation","sql-injection-guard","api-error"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}