Skip to content

Expressions: Filter builder

Eugene Lazutkin edited this page Apr 23, 2026 · 1 revision

Expressions: Filter builder

See also (AWS JS SDK v3): Filter expressions · QueryCommand / ScanCommand. Vocabulary: Concepts.

Two helpers for building FilterExpression. Both are additive builders — they mutate a params object in place and return it, so they compose with every other expressions/ helper against the same params.

  • buildSearch(searchable, query, options?, params?) — substring search over searchablePrefix + <field> mirror columns.
  • buildFilterByExample(example, params?) — equality filter from a partial object.

Both pure functions. Mutate-and-return a params object.

3.7.0 rename. Was buildFilter in 3.6.x. Renamed to buildSearch to disambiguate from the adapter's applyFilter, which compiles structured ?<op>-<field>=<value> URL-grammar clauses (a different FilterExpression pattern).

buildSearch

import {buildSearch} from 'dynamodb-toolkit/expressions';

const params = buildSearch(
  {name: 1, climate: 1, terrain: 1},   // searchable fields
  'tooine',                            // query
  {prefix: '-search-', caseSensitive: false},
  {/* existing params */}
);
// FilterExpression: "contains(#sr0, :flt0) OR contains(#sr1, :flt0) OR contains(#sr2, :flt0)"
// EAN: {#sr0: '-search-name', #sr1: '-search-climate', #sr2: '-search-terrain'}
// EAV: {:flt0: 'tooine'}

The pattern: write -search-<field> mirror columns containing lowercase versions of your searchable fields. The -search- prefix is the Adapter's searchablePrefix default — the one prefix the toolkit does reserve (configurable; see Concepts → searchablePrefix). When the Adapter is declared with searchable: {...} and technicalPrefix: '_', the built-in prepare step writes the mirrors automatically — no custom hook needed. For adapters without technicalPrefix, a user prepare hook can mirror manually:

hooks: {
  prepare(item) {
    const out = {...item};
    for (const k of Object.keys(this.searchable || {})) {
      if (k in item) out['-search-' + k] = String(item[k]).toLowerCase();
    }
    return out;
  }
}

Then buildSearch matches case-insensitively without scanning the original fields.

Options

interface SearchOptions {
  fields?: string | string[] | null;  // restrict search to a subset of searchable
  prefix?: string;                    // mirror column prefix; default '-search-'
  caseSensitive?: boolean;            // default false (toLowerCase the query)
}

fields lets the caller restrict which searchable columns are scanned per request — useful when the user has narrowed the projection to a subset.

buildFilterByExample

Builds an equality FilterExpression from a partial object:

import {buildFilterByExample} from 'dynamodb-toolkit/expressions';

const params = buildFilterByExample({climate: 'frozen', gravity: '1 standard'});
// FilterExpression: "#fbe0 = :fbe0 AND #fbe1 = :fbe1"
// EAN/EAV: filled with key/value pairs

Useful when callers want exact-match semantics over a partial item — e.g. a list-op pre-filter that narrows to rows matching a known shape. Separate from the REST surface: REST's ?<op>-<field>=<value> grammar goes through adapter.applyFilter(params, clauses), not this helper.

Combining with KeyCondition

FilterExpression runs after DynamoDB has matched against KeyConditionExpression and before items are returned to the SDK. It does not reduce the read units consumed — DynamoDB still reads everything the key condition selects, then drops items the filter rejects.

For high-throughput access patterns prefer narrow KeyConditionExpression over broad scans + filters.

Clone this wiki locally