brianc/node-postgres · error · Error
circular reference detected while preparing "' + val + '" fo
Error message
circular reference detected while preparing "' + val + '" for query
What it means
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.
Source
Thrown at packages/pg/lib/utils.js:76
return dateToStringUTC(val)
} else {
return dateToString(val)
}
}
if (Array.isArray(val)) {
return arrayString(val)
}
return prepareObject(val, seen)
}
return val.toString()
}
function prepareObject(val, seen) {
if (val && typeof val.toPostgres === 'function') {
seen = seen || []
if (seen.indexOf(val) !== -1) {
throw new Error('circular reference detected while preparing "' + val + '" for query')
}
seen.push(val)
return prepareValue(val.toPostgres(prepareValue), seen)
}
return JSON.stringify(val)
}
function dateToString(date) {
let offset = -date.getTimezoneOffset()
let year = date.getFullYear()
const isBCYear = year < 1
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
let ret =
String(year).padStart(4, '0') +
'-' +View on GitHub (pinned to ff9d775abd)
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.
Example fix
// before — toPostgres returns self, creating a cycle
const obj = { value: 1 }
obj.toPostgres = function () { return obj } // circular!
await client.query('SELECT $1::json', [obj])
// after — return a plain serializable value
const obj = { value: 1 }
obj.toPostgres = function () { return JSON.stringify({ value: this.value }) }
await client.query('SELECT $1::json', [obj]) Defensive patterns
Strategy: validation
Validate before calling
const { prepareValue } = require('pg/lib/utils')
function hasCircularToPostgres(val, seen = new WeakSet()) {
if (!val || typeof val !== 'object') return false
if (seen.has(val)) return true
if (typeof val.toPostgres !== 'function') return false
seen.add(val)
try {
const result = val.toPostgres(prepareValue)
if (result === val) return true
if (Array.isArray(result)) return result.some((item) => hasCircularToPostgres(item, seen))
if (typeof result === 'object' && result !== null) return hasCircularToPostgres(result, seen)
} catch {
// toPostgres may throw during probing — treat as non-circular for safety
}
return false
}
// Usage before query:
if (hasCircularToPostgres(myParam)) {
throw new Error('Query parameter has a circular toPostgres reference')
} Type guard
// Check that toPostgres does not cycle back to the same object
function isSafeToPostgresObject(val) {
if (!val || typeof val !== 'object') return true
if (typeof val.toPostgres !== 'function') return true
const result = val.toPostgres(() => null)
return result !== val
} Try / catch
try {
await client.query(text, [param])
} catch (err) {
if (err.message.includes('circular reference detected while preparing')) {
throw new Error('A query parameter has a circular toPostgres() reference — fix the custom serializer')
}
throw err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of brianc/node-postgres@ff9d775abd (2026-08-11).
Data as JSON: /api/errors/0a585afd3b1182c9.
Report an issue: GitHub.