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

  1. 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.
  2. If toPostgres() returns an array or object, verify it does not contain the original object anywhere in its structure.
  3. Replace toPostgres() with JSON.stringify-friendly structure if custom serialization is not strictly needed.
  4. 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

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.