clockworklabs/SpacetimeDB · error · TypeError

Index '${indexLabel}' on table '${tableLabel}' must define a

Error message

Index '${indexLabel}' on table '${tableLabel}' must define a non-empty 'accessor'

What it means

When table.ts converts the user-declared options.indexes entries into raw index metadata, each entry needs a non-empty accessor string - it becomes the property name under the table handle used for index-scoped queries. An entry with a missing or empty accessor throws a TypeError naming the table and index.

Source

Thrown at crates/bindings-typescript/src/lib/table.ts:442

        colId: colIds.get(name)!,
        value: writer.getBuffer(),
      });
    }

    // If this column is shaped like ScheduleAtAlgebraicType, mark it as the schedule‑at column
    const algebraicType = builder.typeBuilder.algebraicType;
    if (ScheduleAt.isScheduleAt(algebraicType)) {
      scheduleAtCol = colIds.get(name)!;
    }
  }

  // convert explicit multi‑column indexes coming from options.indexes
  for (const indexOpts of userIndexes ?? []) {
    const accessor = indexOpts.accessor;
    if (typeof accessor !== 'string' || accessor.length === 0) {
      const tableLabel = name ?? '<unnamed>';
      const indexLabel = indexOpts.name ?? '<unnamed>';
      throw new TypeError(
        `Index '${indexLabel}' on table '${tableLabel}' must define a non-empty 'accessor'`
      );
    }
    let algorithm: RawIndexAlgorithm;
    switch (indexOpts.algorithm) {
      case 'btree':
        algorithm = {
          tag: 'BTree',
          value: indexOpts.columns.map(c => colIds.get(c)!),
        };
        break;
      case 'hash':
        algorithm = {
          tag: 'Hash',
          value: indexOpts.columns.map(c => colIds.get(c)!),
        };
        break;
      case 'direct':

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Add a non-empty accessor to every entry: { name: 'by_email', accessor: 'byEmail', algorithm: 'btree', columns: ['email'] }
  2. Adopt a convention (e.g. accessor equals the camelCased name) so entries are never left without one
  3. Type the options object against the library's exported index-options interface so omissions surface at compile time

Example fix

// before
indexes: [{ name: 'by_email', algorithm: 'btree', columns: ['email'] }]

// after
indexes: [{ name: 'by_email', accessor: 'byEmail', algorithm: 'btree', columns: ['email'] }]
Defensive patterns

Strategy: validation

Validate before calling

function validateIndexOptions(indexes: Array<{ name?: string; accessor?: unknown }>): void {
  for (const idx of indexes) {
    if (typeof idx.accessor !== 'string' || idx.accessor.length === 0) {
      throw new TypeError(`index '${idx.name ?? '<unnamed>'}' must define a non-empty 'accessor'`);
    }
  }
}

Prevention

When it happens

Trigger: Declaring a table with indexes: [{ name: 'by_email', algorithm: 'btree', columns: ['email'] }] - no accessor field - or accessor: ''.

Common situations: Copying index examples from older docs where accessor was optional or inferred; renaming accessors and leaving one empty; hand-writing the options object without the compiler checking it.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/88fb7542690aff9e. Report an issue: GitHub.