remix-run/remix · error · Error

Composite primary keys require an object value

Error message

Composite primary keys require an object value

What it means

When a table has a composite primary key, row lookups require the key value to be a plain object mapping each key column to its value. This error is thrown when the provided value is a primitive, null, or an array while multiple key columns are expected.

Source

Thrown at packages/data-table/src/lib/table.ts:1026

/**
 * Normalizes a primary-key input into an object keyed by primary-key columns.
 * @param table Source table.
 * @param value Primary-key input value.
 * @returns Primary-key object.
 */
export function getPrimaryKeyObject<table extends AnyTable>(
  table: table,
  value: PrimaryKeyInput<table>,
): Partial<TableRow<table>> {
  let keys = getTablePrimaryKey(table)

  if (keys.length === 1 && (typeof value !== 'object' || value === null || Array.isArray(value))) {
    let key = keys[0] as keyof TableRow<table>
    return { [key]: value } as Partial<TableRow<table>>
  }

  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
    throw new Error('Composite primary keys require an object value')
  }

  let objectValue = value as Record<string, unknown>
  let output: Partial<TableRow<table>> = {}

  for (let key of keys) {
    if (!(key in objectValue)) {
      throw new Error(
        'Missing key "' + key + '" for primary key lookup on "' + getTableName(table) + '"',
      )
    }

    ;(output as Record<string, unknown>)[key] = objectValue[key]
  }

  return output
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Pass an object with every primary key column: { tenantId, postId }
  2. If the table really should have a single key, fix the table definition's primaryKey
  3. Search call sites for scalar lookups after changing a primaryKey to composite

Example fix

// before
let row = await db.members.find('tenant-1')
// after
let row = await db.members.find({ tenantId: 'tenant-1', memberId: 'm-9' })
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new TypeError('expected key object')

Type guard

const isKeyObject = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v)

Prevention

When it happens

Trigger: Calling a find/lookup API like db.posts.find('a') on a table whose primaryKey is ['tenantId','postId'], or passing an array of values instead of an object.

Common situations: Migrating a single-column id table to a composite key but keeping old scalar lookup call sites; forgetting the composite-key convention after reading docs written for id tables.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/8622f44d7879f471. Report an issue: GitHub.