# Filtering records (/docs/framework/guides/filtering)



<SalesforceReference title="WHERE" href="https://developer.salesforce.com/docs/platform/salesforce-soql-sosl/guide/sforce-api-calls-soql-select-conditionexpression.html" />

## Basic conditions [#basic-conditions]

Repeated `where` calls are joined with `AND`:

```ts
import { db } from "./db";

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Name", "like", "Acme%")
  .where("AnnualRevenue", ">=", 100_000);
```

Pass application values through the builder instead of constructing SOQL strings yourself. Kysoql validates and escapes ordinary API values when it compiles the query.

Do not pre-quote strings. Apex compile-only queries have separate typed bind helpers when you need Apex variables rather than SOQL literals.

## Operators and values [#operators-and-values]

| Operator               | Accepted values                                                                                 |
| ---------------------- | ----------------------------------------------------------------------------------------------- |
| `=`, `!=`              | Values compatible with the generated field. `null` is allowed only where the field supports it. |
| `<`, `<=`, `>`, `>=`   | Ordered field types with non-null values.                                                       |
| `like`                 | Supported string-like fields and a string pattern.                                              |
| `in`, `not in`         | Non-empty homogeneous lists or compatible ID/reference subqueries.                              |
| `includes`, `excludes` | Non-empty generated active values for multipicklist fields.                                     |

Operator names are lowercase in TypeScript. Compiled SOQL uses the normal uppercase Salesforce keywords.

For null checks, use `where("Field", "=", null)` or `"!="`. Do not pass the string `"null"`.

Date and time fields use [typed literal helpers](/docs/framework/guides/literals). Location fields use [distance expressions](/docs/framework/guides/geolocation).

## Group `AND`, `OR`, and `NOT` [#group-and-or-and-not]

The expression builder can be called as `eb(field, operator, value)` and also provides `and`, `or`, and `not`:

```ts
import { db } from "./db";

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where((eb) =>
    eb.and([
      eb.or([
        eb("Name", "like", "Acme%"),
        eb("Name", "like", "Example%"),
      ]),
      eb.not(eb("Name", "like", "%Test%")),
    ]),
  );
```

Keep boolean groups non-empty. If a filter is optional, [`$if`](/docs/framework/guides/composition) is usually clearer than trying to define an empty `and([])` or `or([])`.

## `IN` and `NOT IN` [#in-and-not-in]

```ts
import { db } from "./db";

const ids: readonly string[] = ["001000000000001AAA"];

if (ids.length === 0) {
  throw new Error("Choose at least one account ID");
}

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Id", "in", ids);
```

Empty lists are rejected. Decide what an empty application input means before building the query. It might mean "no matches", "invalid request", or "skip this optional filter".

Be especially careful with access-control filters. Silently dropping an empty authorization list can widen a query.

For related-record existence checks, use a [semi-join or anti-join](/docs/framework/guides/relationships#semi-joins-and-anti-joins) instead of fetching IDs into JavaScript first.

## Multipicklists [#multipicklists]

This example assumes `Account.Interests__c` is a generated multipicklist with active values `Email` and `Events`:

```ts
import { soqlMultiSelectAnd } from "@kysoql/core";
import { db } from "./db";

const eitherValue = db
  .selectFrom("Account")
  .select(["Id", "Interests__c"])
  .where("Interests__c", "includes", ["Email", "Events"]);

const bothValues = db
  .selectFrom("Account")
  .select(["Id", "Interests__c"])
  .where(
    "Interests__c",
    "includes",
    soqlMultiSelectAnd("Email", "Events"),
  );
```

A normal array uses Salesforce's OR form. `soqlMultiSelectAnd(...)` produces the AND form while staying typed against generated active picklist values.

Apex also has a separate [bind-left `INCLUDES` form](/docs/core/apex/bindings#bind-left-includes).

## Treat `LIKE` patterns deliberately [#treat-like-patterns-deliberately]

Plain strings keep Salesforce `LIKE` behaviour, so `%` and `_` remain wildcard characters:

```ts
import { db } from "./db";

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Name", "like", "Acme_%");
```

When user input should be matched literally, use `soql.likeLiteral()`:

```ts
import { soql } from "@kysoql/core";
import { db } from "./db";

const search = "50%_off";

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Name", "like", soql.likeLiteral(search));
```

Kysoql validates identifiers and escapes values, but that is not authorization. If users can choose fields or filters, keep an application allowlist for what they are allowed to query. See [security and production use](/docs/core/reference/security).

## Raw SOQL escape hatch [#raw-soql-escape-hatch]

Use `soql.raw()` only for trusted syntax the builder does not model:

```ts
import { soql } from "@kysoql/core";
import { db } from "./db";

const query = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where(soql.raw("CALENDAR_YEAR(CreatedDate) = 2026"));
```

Raw fragments bypass normal identifier validation and value escaping. Never interpolate request data, search terms, IDs, form values, or other untrusted input into them.

## Filter with functions [#filter-with-functions]

Expression callbacks expose supported date functions, `toLabel`, and `distance` through `eb.fn`. Beta arithmetic lives under `eb.beta` so experimental syntax is easy to spot.

Continue with [date functions](/docs/framework/guides/date-functions), [translated labels](/docs/framework/guides/select-functions#translated-labels), or [Beta and pilot features](/docs/core/advanced/experimental).
