Skip to content

Query String Syntax

RzR edited this page May 1, 2026 · 1 revision

Query String Syntax

This page documents the grammar accepted by PagedRequestQueryParser, used by both the MVC binder ([FromPagedQuery]) and the minimal-API wrappers (PagedQuery<T>, PagedQueryWithFilters<T>). The parser populates a PagedRequest (or PageRequestWithFilters) instance from an IQueryCollection, optionally constrained by a PageableMetadata allow-list.

Recognized keys

The constants are defined in PagedQueryKeys.

Key Bound to Notes
page PagedRequest.Page Integer >= 1. Falls back to 1 if missing or invalid.
pageSize PagedRequest.PageSize Integer >= 1. Capped to PagedListResultWebOptions.MaxPageSize. Falls back to PagedListResultWebOptions.DefaultPageSize if missing.
search PagedRequest.Search.Search Free-text term.
searchAll PagedRequest.Search.SearchInAllTextFields / SearchInAllFields Accepts true / text / fields (search across text fields), all (search across every property), or false / none (limit to searchFields).
searchFields PagedRequest.Search.CustomSearchTextProperties Comma-separated list of property names. Filters out names that are not in the allow-list (when present).
order PagedRequest.Order.OrderByProperty / OrderDirection / OrderByDefaultProperty Format <property> or <property>:<dir> where <dir> is asc / desc. The literal value default activates OrderByDefaultProperty = true.
filter PageRequestWithFilters.Filters Repeatable. See "Filter grammar" below.
fields PagedRequest.Fields Comma-separated list of property names to project.
predefinedField PagedRequest.PredefinedRecord.PredefinedFieldName Property name to match against.
predefinedRecords PagedRequest.PredefinedRecord.PredefinedRecords Comma-separated list of identifiers.

The parser is case-insensitive on keys and on operator names. Whitespace around values is trimmed.

Filter grammar

filter = <propertyName> ":" <operator> [ ":" <value> [ "," <value2> ... ] ]

The operator is the textual name of a FilterType value (e.g. Equals, Between, IsIn). The number of accepted values depends on the operator:

Operator family Values CompareValue
Equals, NotEquals, StartsWith, DoesNotStartWith, EndsWith, DoesNotEndsWith, Contains, SensitiveContains, DoesNotContains, SensitiveDoesNotContains, GreaterThan, GreaterThanOrEquals, LessThan, LessThanOrEquals Exactly 1 Ignored
Between Exactly 1 (lower bound) Required (upper bound)
IsIn, IsNotIn 1 or more, comma-separated Ignored
IsNull, IsNotNull None Ignored

Examples:

filter=status:Equals:active
filter=price:Between:10,100
filter=tags:IsIn:new,featured,sale
filter=archivedOn:IsNull

To send several filters, repeat the filter key:

?filter=status:Equals:active&filter=price:Between:10,100

The top-level link between filters is the filterLink argument of GetPagedWithFiltersAsync (default And). To express child conditions linked to a parent (the Dependencies collection), use a body-bound request: the query string is intentionally limited to the flat case.

Per-row error keys

When a filter row cannot be parsed, the binder writes an entry into ModelStateDictionary (or into PagedQuery<T>.Errors) with the key filter[i], where i is the zero-based index of the offending row in the original query string. This makes it easy to surface a precise validation error in the response:

{
  "type": "https://datatracker.ietf.org/doc/html/rfc9457",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "filter[1]": [ "Operator 'Between' requires a CompareValue (upper bound)." ]
  }
}

Allow-list interaction

When PageableMetadata is registered for the bound entity (via ConfigurePageable<T>), the parser enforces:

  • order is rejected if the property is not in the AllowedSort set. The error key is order.
  • filter[i] is rejected if the property is not in the AllowedFilter set.
  • searchFields entries are filtered: any name not in AllowedSearch is dropped silently.

If no metadata is registered for the entity (or [FromPagedQuery] is used without EntityType), the parser does not enforce any allow-list and accepts all property names.

See Allow List Registry.

Manual usage

You can call the parser yourself if you need to bind from a non-HTTP source (background job, queue worker, etc.):

var request = new PageRequestWithFilters();
PagedRequestQueryParser.Populate(
    request,
    httpContext.Request.Query,
    options: new PagedListResultWebOptions { MaxPageSize = 200 },
    allowList: registry.Get(typeof(Product))); // optional

The result is the populated request instance plus a PagedParseResult containing the per-key errors (Errors is a Dictionary<string, string> keyed by the key name described above).

Worked example

Given the following request:

GET /products
    ?page=2
    &pageSize=20
    &search=usb
    &searchAll=text
    &order=price:desc
    &filter=status:Equals:active
    &filter=price:Between:10,100
    &fields=Id,Name,Price
    &predefinedField=Id
    &predefinedRecords=1,2,3

The bound PageRequestWithFilters will look like:

{
  "page": 2,
  "pageSize": 20,
  "search": {
    "search": "usb",
    "searchInAllTextFields": true,
    "searchInAllFields": false,
    "customSearchTextProperties": []
  },
  "order": { "orderByProperty": "price", "orderDirection": 1, "orderByDefaultProperty": false },
  "fields": [ "Id", "Name", "Price" ],
  "predefinedRecord": {
    "predefinedFieldName": "Id",
    "predefinedRecords": [ "1", "2", "3" ]
  },
  "filters": [
    { "filterValue": { "condition": 0, "propertyName": "status", "values": [ "active" ] }, "filterApplyOrder": 0 },
    { "filterValue": { "condition": 14, "propertyName": "price", "values": [ "10" ], "compareValue": "100" }, "filterApplyOrder": 1 }
  ]
}

(condition: 14 is Between. See Data Models Reference for the full enum table.)

Clone this wiki locally