{"record":{"id":"0a585afd3b1182c9","repo":"brianc/node-postgres","slug":"circular-reference-detected-while-preparing-v","errorCode":null,"errorMessage":"circular reference detected while preparing \"' + val + '\" for query","messagePattern":"circular reference detected while preparing \"' \\+ val \\+ '\" for query","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pg/lib/utils.js","lineNumber":76,"sourceCode":"        return dateToStringUTC(val)\n      } else {\n        return dateToString(val)\n      }\n    }\n    if (Array.isArray(val)) {\n      return arrayString(val)\n    }\n\n    return prepareObject(val, seen)\n  }\n  return val.toString()\n}\n\nfunction prepareObject(val, seen) {\n  if (val && typeof val.toPostgres === 'function') {\n    seen = seen || []\n    if (seen.indexOf(val) !== -1) {\n      throw new Error('circular reference detected while preparing \"' + val + '\" for query')\n    }\n    seen.push(val)\n\n    return prepareValue(val.toPostgres(prepareValue), seen)\n  }\n  return JSON.stringify(val)\n}\n\nfunction dateToString(date) {\n  let offset = -date.getTimezoneOffset()\n\n  let year = date.getFullYear()\n  const isBCYear = year < 1\n  if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation\n\n  let ret =\n    String(year).padStart(4, '0') +\n    '-' +","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/brianc/node-postgres/blob/ff9d775abd12f29dd6df03945253b54eabbb29f2/packages/pg/lib/utils.js#L58-L94","documentation":"Thrown by prepareObject() when serializing a query parameter that has a toPostgres() method which produces a circular reference. The function tracks objects being serialized in a 'seen' array; if the same object appears again during recursive preparation, it means toPostgres() returned a value that cycles back to the original object, which would cause infinite recursion without this guard.","triggerScenarios":"At utils.js:72-76, an object with a toPostgres() method is being serialized. The 'seen' array is checked: if seen.indexOf(val) !== -1, the object is already in the serialization chain. This happens when obj.toPostgres(prepareValue) returns obj itself, or returns a collection/array that contains obj, causing prepareValue to re-enter prepareObject with the same object.","commonSituations":"A custom type whose toPostgres() method accidentally returns itself (e.g., this.toPostgres = () => this); an object whose toPostgres() returns an array containing the original object; a nested data structure where toPostgres() delegates to another object that cycles back; ORM or custom serialization logic with a feedback loop.","solutions":["Audit the toPostgres() method of the object being passed as a query parameter — ensure it returns a primitive, string, or a new object that does not reference the original.","If toPostgres() returns an array or object, verify it does not contain the original object anywhere in its structure.","Replace toPostgres() with JSON.stringify-friendly structure if custom serialization is not strictly needed.","Test the custom type's toPostgres() in isolation to confirm it terminates and returns a serializable value."],"exampleFix":"// before — toPostgres returns self, creating a cycle\nconst obj = { value: 1 }\nobj.toPostgres = function () { return obj } // circular!\nawait client.query('SELECT $1::json', [obj])\n\n// after — return a plain serializable value\nconst obj = { value: 1 }\nobj.toPostgres = function () { return JSON.stringify({ value: this.value }) }\nawait client.query('SELECT $1::json', [obj])","handlingStrategy":"validation","validationCode":"const { prepareValue } = require('pg/lib/utils')\n\nfunction hasCircularToPostgres(val, seen = new WeakSet()) {\n  if (!val || typeof val !== 'object') return false\n  if (seen.has(val)) return true\n  if (typeof val.toPostgres !== 'function') return false\n  seen.add(val)\n  try {\n    const result = val.toPostgres(prepareValue)\n    if (result === val) return true\n    if (Array.isArray(result)) return result.some((item) => hasCircularToPostgres(item, seen))\n    if (typeof result === 'object' && result !== null) return hasCircularToPostgres(result, seen)\n  } catch {\n    // toPostgres may throw during probing — treat as non-circular for safety\n  }\n  return false\n}\n\n// Usage before query:\nif (hasCircularToPostgres(myParam)) {\n  throw new Error('Query parameter has a circular toPostgres reference')\n}","typeGuard":"// Check that toPostgres does not cycle back to the same object\nfunction isSafeToPostgresObject(val) {\n  if (!val || typeof val !== 'object') return true\n  if (typeof val.toPostgres !== 'function') return true\n  const result = val.toPostgres(() => null)\n  return result !== val\n}","tryCatchPattern":"try {\n  await client.query(text, [param])\n} catch (err) {\n  if (err.message.includes('circular reference detected while preparing')) {\n    throw new Error('A query parameter has a circular toPostgres() reference — fix the custom serializer')\n  }\n  throw err\n}","preventionTips":["Ensure toPostgres() always returns a new primitive, string, or plain object — never the original object (this).","If toPostgres() returns an array, verify it does not contain the original object.","Test custom serialization types in isolation before using them as query parameters.","Prefer JSON.stringify-based serialization over complex toPostgres() logic when possible."],"tags":["query-parameters","serialization","circular-reference","programming-error","topostgres"],"backgroundTag":null,"analyzedSha":"ff9d775abd12f29dd6df03945253b54eabbb29f2","analyzedAt":"2026-08-11T15:33:59.644Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}