# ag-grid-url-sync: the query-string grammar

ag-grid-url-sync encodes an AG Grid filter as one query parameter per column: a prefix, the column id, an underscore, and one of 18 operation tokens, then the value. The 26 native text, number and date operations map onto those tokens; equals, not-equal, blank and not-blank are shared, and the parser infers their type from the value. This page is the exact grammar for version 0.3.0: tokens, value formats, validation rules, and the grouped single-parameter mode.

Published: 2026-09-25
Canonical: https://umar.codes/ag-grid-url-sync-grammar

## Revisions

- 2026-09-15 — Created; written against the published 0.3.0 package source, not the README.
- 2026-09-25 — Published. Defaults and the 18 tokens re-checked against the 0.3.0 package, still the current npm release; added a link to the URL-as-state essay.

---

A filter in [ag-grid-url-sync](/ag-grid-url-sync) is one query parameter:
`f_` prefix, column id, underscore, operation token, `=`, value. So
`f_salary_gte=80000` means the salary column is filtered to values of 80,000
or more. 18 tokens cover all 26 AG Grid text, number and date operations,
because four tokens are shared across types. This page is the grammar for
version 0.3.0.

## One parameter per column, split on the last underscore

The parameter name is `<prefix><column>_<operation>`. The prefix defaults to
`f_` and is configurable; the parser adds a trailing underscore if the
configured prefix lacks one. Everything after the prefix is split on the
**last** underscore, so column ids may contain underscores and operation
tokens never do. `f_first_name_startsWith=jo` parses as column `first_name`,
operation `startsWith`, value `jo`.

Parameters that do not start with the prefix are ignored, so the filter
parameters can share a URL with anything else. Each column appears once;
the grammar has no way to express two conditions on one column, and no
AND/OR operator. A parameter that fails to parse is reported through the
`onParseError` callback and skipped, and parsing continues with the rest.

## The 18 operation tokens

Internal operation names are longer than the tokens in the URL. The token
is what you type; the internal name is what the library's TypeScript types
use; the AG Grid name is what reaches the grid's filter model.

| Token | Internal name | AG Grid name | Applies to |
| --- | --- | --- | --- |
| `contains` | contains | contains | text |
| `notContains` | notContains | notContains | text |
| `startsWith` | startsWith | startsWith | text |
| `endsWith` | endsWith | endsWith | text |
| `eq` | eq | equals | text, number, date |
| `neq` | notEqual | notEqual | text, number, date |
| `blank` | blank | blank | text, number, date |
| `notBlank` | notBlank | notBlank | text, number, date |
| `lt` | lessThan | lessThan | number |
| `lte` | lessThanOrEqual | lessThanOrEqual | number |
| `gt` | greaterThan | greaterThan | number |
| `gte` | greaterThanOrEqual | greaterThanOrEqual | number |
| `range` | inRange | inRange | number |
| `before` | dateBefore | lessThan | date |
| `beforeEq` | dateBeforeOrEqual | lessThanOrEqual | date |
| `after` | dateAfter | greaterThan | date |
| `afterEq` | dateAfterOrEqual | greaterThanOrEqual | date |
| `daterange` | dateRange | inRange | date |

Counting per type gives AG Grid's 26: 8 text, 9 number and 9 date, with
`eq`, `neq`, `blank` and `notBlank` counted once per type. Every token
matches its internal name except the eleven shortened ones, and `eq` is the
only token that AG Grid itself spells differently, as `equals`.

Date operations reuse AG Grid's comparison names because AG Grid's date
filter has no `before` or `after` of its own. The URL grammar introduces
those words so a link reads as a sentence: `f_deadline_before=2026-12-31`.

## Values: text is encoded, numbers and dates are not

**Text values** are passed through `encodeURIComponent` when a URL is
generated, so `f_name_contains=O%27Brien` is the encoded form of `O'Brien`.
On parsing, the value is taken as-is from the URL's search parameters and
checked against a maximum length, 200 characters by default and configurable
through `maxValueLength`. Anything longer is a parse error.

**Number values** must match a strict pattern: an optional sign, digits with
an optional fraction, or a leading-dot fraction, and an optional exponent.
`42`, `-3.5`, `.5` and `1e6` pass. `42abc` fails, because the library checks
the pattern before calling `parseFloat`, which would otherwise accept it. An
empty value fails. The magnitude must not exceed `Number.MAX_SAFE_INTEGER`.
Numbers are written without encoding.

**Date values** must be exactly `YYYY-MM-DD`, ten characters. The library
then constructs the date in UTC and checks that year, month and day round-trip,
so `2026-02-30` is rejected even though `Date` would silently roll it into
March. Dates are written without encoding; the hyphens are safe.

**Blank operations** ignore the value. The generator writes `=true`, so
`f_notes_blank=true`, and the parser accepts any value or none. The filter
applied to the grid carries an empty string.

## Ranges use a comma and exactly two parts

`range` and `daterange` take `min,max` and `start,end`. The comma is written
literally, not encoded, and the parser splits on it and requires exactly two
parts. Each part is validated by the number or date rule above, and the first
value must be no greater than the second, for numbers and for dates.

```
f_age_range=25,45
f_period_daterange=2026-01-01,2026-12-31
```

A range with one part, three parts, or an empty side is a parse error for
that column only. Date ranges reach the grid as `inRange` with `dateFrom` and
`dateTo`; number ranges as `inRange` with `filter` and `filterTo`.

## Shared tokens infer their type from the value

`eq`, `neq`, `blank` and `notBlank` are valid on text, number and date
columns, and the URL does not say which. The parser decides from the value,
in this order:

1. If the value is a valid ISO date, the filter is a date filter.
2. Else if `parseFloat` returns a finite number within the safe-integer
   range, the filter is a number filter.
3. Else it is a text filter.

`blank` and `notBlank` always parse as text filters, since there is no value
to inspect. Two consequences follow. `f_status_eq=2026-01-15` on a text
column parses as a date filter, and `f_id_eq=42abc` parses as a number
filter with value 42, because step 2 uses `parseFloat` and not the strict
pattern the number-only tokens use. In practice the grid's own column
definition decides how the value is used, so both cases behave when the
column type matches the value, and the detection only matters when it does
not.

## Grouped mode packs every filter into one parameter

The default is one parameter per column, called individual mode. Grouped
mode instead writes a single parameter, `grid_filters` by default and
configurable through `groupedParam`, in one of three formats:

| Format | What the value holds | Example, decoded |
| --- | --- | --- |
| `querystring` (default) | The individual-mode string, percent-encoded once more | `f_name_contains=john&f_age_gt=25` |
| `json` | The library's filter-state object as JSON | `{"name":{"filterType":"text","type":"contains","filter":"john"}}` |
| `base64` | The JSON format, base64-encoded | `eyJuYW1lIjp7...` |

The querystring format is the same grammar as this page, so a grouped URL is
readable once decoded. The JSON format uses the internal operation names
from the table above, not the URL tokens: `"type":"greaterThan"`, not
`"type":"gt"`. On parsing, the library checks for a grouped parameter first
and detects its format, trying base64 before JSON before querystring; if
none is present it falls back to individual mode. A URL cannot mix the two.

## What the grammar does not cover

Only the three column filter types are encoded. The grammar has no form for
AG Grid's set filter, for multi-condition filters joined with AND or OR, for
the floating-filter state, or for sort order and column arrangement. Those
are out of scope by design: the library is for [sharing a filtered view as a
link](/url-as-state-manager), and a link that also pinned column widths would break the moment the
recipient's grid differed.

The columns it applies to are detected from the grid's own definitions:
`agTextColumnFilter`, `agNumberColumnFilter` and `agDateColumnFilter` first,
then `cellDataType` of `date`, `number` or `text`. A parameter naming a
column the grid does not have parses successfully and is skipped when the
filters are applied.

## Version and stability

This page describes version 0.3.0, the current release on npm, and was
written from the published package source rather than the README. When the
grammar changes, this page is revised and the revision log says what moved.
