---
title: "Operators"
description: "OQL operator reference. Comparison, ranges, membership, pattern matching, and null checks."
canonical_url: https://developer.onepagecrm.com/oql/operators/
source: Markdown mirror of https://developer.onepagecrm.com/oql/operators/
---

OQL operators live inside `where` clauses. A bare value means equality;
operators are only required for everything else.

```jsonc
"where": {
  "status": "won",                          // implicit =
  "amount": { ">": 1000 },                  // explicit operator
  "owner_id": { "in": ["abc...", "def..."] }
}
```

## Reference

| Operator | Example |
|----------|---------|
| `=` (implicit) | `{ "status": "active" }` |
| `=` (explicit) | `{ "status": { "=": "active" } }` |
| `!=` | `{ "status": { "!=": "lost" } }` |
| `<`, `>`, `<=`, `>=` | `{ "amount": { ">": 1000 } }` |
| `in` | `{ "status": { "in": ["won", "lost"] } }` |
| `between` | `{ "amount": { "between": [100, 500] } }` |
| `like` | `{ "first_name": { "like": "Al%" } }` |
| `is` (null check) | `{ "owner_id": { "is": null } }` |
| `is not` (null check) | `{ "owner_id": { "is not": null } }` |

## Type compatibility

Not every operator works on every type. OQL enforces these rules and
rejects incompatible combinations with a clear error.

| Operator | string | number | boolean | date | time | id | string[] | array |
|----------|:------:|:------:|:-------:|:----:|:----:|:--:|:--------:|:-----:|
| `=` | Y | Y | Y | Y | Y | Y | Y | Y |
| `!=` | Y | Y | Y | Y | Y | Y | Y | — |
| `<` `>` `<=` `>=` | — | Y | — | Y | Y | — | — | — |
| `in` | Y | Y | — | — | — | Y | Y | — |
| `between` | — | Y | — | Y | Y | — | — | — |
| `like` | Y | — | — | — | — | — | — | — |
| `is` / `is not` (null) | Y | Y | Y | Y | Y | Y | — | Y |

`array` covers embedded arrays such as `emails`, `phones`, and `urls`
on contacts. Each exposes string subfields you can filter against;
see below.

## Notes per operator

### Equality

Bare values imply `=`. The two forms below are equivalent.

```jsonc
// bare value
{ "status": "won" }

// explicit operator
{ "status": { "=": "won" } }
```

### `!=` keeps null and missing

`!=` excludes records where the field equals the given value, but it
**keeps** records where the field is null or missing — an absent value
counts as "not equal" to anything. So `!=` is not the same as "has
some other value." When you also need the field present, add a null
check:

```jsonc
{ "company_id": { "!=": "507f..." } }   // not that company — also matches contacts with no company
{ "company_id": { "is not": null } }    // linked to some company
{ "company_id": { "is": null } }        // no linked company
```

### `between`

Lower bound inclusive. The upper bound depends on what you pass:

- A **literal** upper bound is taken **exactly** and inclusively (`<=` the
  value given). On a `:date` field that covers the whole day; on a `:time`
  field it's that precise instant — pass an explicit end instant if you want
  a full day.
- A **date-function** upper bound (e.g. `TODAY()`) uses that period's
  half-open range, so it stops just before the first instant *after* the
  period. See [Functions](/oql/functions/).

Keeping literal bounds exact stops sub-day ranges from over-returning, while
calendar ranges stay natural.

```jsonc
{ "amount": { "between": [100, 500] } }                          // 100–500 inclusive
{ "close_date": { "between": ["2026-01-01", "2026-03-31"] } }    // Q1, both days included
{ "call_time": { "between": [{ "DAYS_AGO": [30] }, "TODAY()"] } } // last 30 days, through end of today
```

### `like`

String-only pattern match. Use `%` for any-length wildcard and `_` for
a single character. Anchored on both ends. Case insensitive.

```jsonc
{ "first_name": { "like": "Al%" } }      // starts with "Al"
{ "company":    { "like": "%Ltd" } }     // ends with "Ltd"
{ "phones.number": { "like": "+353%" } } // Irish phone numbers
```

Constraints:

- Maximum 3 wildcards (`%` or `_`) per pattern.
- Maximum 100 characters per pattern.
- Empty patterns are rejected; use `= ""` or `{"is": null}` instead.

### `in`

Membership against a list. The list must be non-empty.

```jsonc
{ "status":  { "in": ["won", "pending"] } }
{ "tags":    { "in": ["VIP", "Hot"] } }   // matches if any tag overlaps
```

### `is null` / `is not null`

Explicit null checks, separate from `!=`. For embedded array fields
(emails, phones, urls), `is null` matches records with an empty or
missing array; `is not null` matches records with at least one
element.

```jsonc
{ "owner_id": { "is": null } }
{ "emails":   { "is not": null } }
```

## Filtering on embedded arrays

`emails`, `phones`, and `urls` on contacts are arrays of objects.
Filter using dotted subfield syntax. The match is satisfied if any
element matches.

```jsonc
{ "emails.address": "alice@example.com" }
{ "phones.number":  { "like": "+353%" } }
{ "urls.type":      "linkedin" }
```

Each entity page lists the subfields available on its array fields.
Filtering by the bare field name (without subfield) only works with
`is null` / `is not null`.
