REST API 07 ๐ Query Parameters for Filtering, Sorting, and Searching
A collection endpoint that returns every row in the table is not an API. It is a database dump with an HTTP wrapper. Real collections grow, and the client almost never wants the whole thing. Query parameters are the mechanism that lets the client narrow the result set, order it, and search within it without changing the endpoint. The three familiesโfiltering, sorting, and searchingโare related but distinct, and the design decisions for each determine whether the API scales to large datasets or collapses under its own weight.
The distinction between filtering and searching is the one that matters most. A filter is a precise predicate against a known field: status=active, price_lt=100, created_at_gte=2026-01-01. The client knows the field name and the comparison. A search is a fuzzy match against an unspecified set of text fields: q=leather+wallet. The client does not know which field will match, and the server decides. Filtering is deterministic and indexable; searching is approximate and usually requires a full-text index or a LIKE scan. Mixing the two into a single parameterโor pretending that a search is a filterโproduces an API that is either imprecise or unindexable.
Key point: Filtering uses field-scoped predicates with operators (eq, lt, gte, in), sorting uses an explicit sort or orderby parameter with direction, and searching uses a single q or search parameter that the server resolves across multiple fields. The three compose: a request can filter, sort, and search in the same URL. Offset pagination works for small datasets and page navigation; cursor pagination is the correct choice for large datasets and sequential traversal.
Why filtering needs an operator syntax
The equality-only problem. The simplest filter is a direct field-value pair: ?status=active. It works for exact matches and it is what most simple APIs expose. But it cannot express “price less than 100” or “name contains ‘milk'” or “status is one of active, pending, or review.” The API either adds more parameters (price_lt, price_gt, status_in) or adopts a structured syntax. The Microsoft Azure guidelines use a structured filter expression: filter=name eq 'Milk' and price lt 2.55 . The Spree Commerce API uses prefixed operators: q[price_gte]=20 and q[name_cont]=shirt . The Stripe API uses separate parameters for each field and enum: status, created_gte, created_lte . There is no single standard, but the pattern is the same: the client must be able to express more than equality.
The AND/OR problem. Multiple filters in a request are usually combined with AND. The Spree documentation states explicitly that multiple filters are combined with AND logic . The Microsoft guidelines allow explicit and and or in the filter expression, with parentheses for grouping . The choice between “AND by default, no OR” and “full boolean expression” is a complexity trade-off. Most APIs choose AND by default and expose OR through a repeated parameter or a separate syntax. The client that needs complex boolean logic is rare; the client that needs “status is active AND price is under 100” is common.
The unsupported-field problem. A filter on a field that does not exist, or that is not indexed, is either a silent no-op or an error. The Microsoft guidelines require an error response (4xx) when the client requests sorting or filtering by an unsupported field, with enough detail for the developer to understand what went wrong . The Spryker API returns a 400 with the error message naming the supported properties when a filter addresses an unsupported field . The alternativeโignoring the filter and returning unfiltered resultsโis worse, because the client believes it is filtering and is actually receiving everything.
a. Sorting and the pagination interaction
Sorting is expressed with a sort or orderby parameter. The value is a field name, optionally followed by a direction. The Microsoft guidelines use a comma-separated list of expressions with optional asc or desc suffixes: orderby=name desc,hireDate . The Stripe API uses an order_by enum with a fixed set of sortable fields . The Cubecart API uses separate sort and order parameters: sort=price&order=desc . The choice is between a single parameter with a compound value and two parameters. The single parameter is more expressiveโit can express multi-field sortingโwhile the two-parameter form is simpler for single-field sorting.
The sorting decision that matters most for correctness is the tie-breaker. When two items have the same value for the sort field, their relative order is undefined unless the API adds a deterministic secondary sort. The Emporix API appends _id:ASC as a tie-breaker when no sorting is provided, and when another sort field is provided, it appends _id:ASC to ensure a stable position . Without a tie-breaker, pagination can return the same item on two different pages or skip an item entirely, because the database’s sort order for equal values is not guaranteed.
Sorting must compose with filtering. The Microsoft guidelines state that sorting MUST compose with filtering such that filter=name eq 'david'&orderby=hireDate returns all people named David sorted by hire date . The composition is the point: the client filters to the relevant set, then orders it. If the two parameters conflictโif the filter excludes items that the sort would have placed firstโthe filter wins, because the result set is the filtered set.
The cost of sorting is the reason many APIs restrict it. The Microsoft guidelines note that it is “unusual for a service to support orderby because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results” . The API that supports sorting on every field is the API that will time out on a million-row collection. The practical approach is to expose sorting only on indexed fields, and to reject requests for unsupported fields with a clear error.
b. Searching as a separate concern
Searching is not filtering with a different operator. It is a different query strategy. The Cubecart API uses a search parameter for LIKE searches across relevant fields: search=leather+wallet . The Stripe API uses value with an “is like” match for value list items . The client does not specify which field to search; the server decides based on the resource’s searchable fields. This is the defining characteristic of a search parameter: the field scope is the server’s responsibility, not the client’s.
The search parameter should not be combined with filtering in a way that confuses the two. A request with status=active&q=alice means “active users whose name or email or username contains ‘alice'”โthe filter narrows the set, the search narrows it further by text. The server applies the filter first (it is indexable) and the search second (it may require a scan). The order matters for performance, but the semantics are clear: the filter is a predicate, the search is a relevance match.
The q or search parameter should not be used as a substitute for a field-specific filter. A request like search=active to find active users is a search for the string “active” in every text field, which may match users whose name contains “active” but are not active. The correct request is status=active. The API documentation should make this distinction explicit, because the client that confuses the two will get wrong results and blame the API.
Complete Example Session
The following session traces a set of requests that filter, sort, and search a product collection, showing the query parameters and the expected result shape.
// ============================================
// PART 1: FILTERING WITH OPERATORS
// ============================================
// Find products in category 5 with a price between 20 and 100
// The operator suffixes _gte and _lte express the range
// Multiple filters combine with AND
GET /api/v1/products?category_id=5&price_gte=20&price_lte=100
// ============================================
// PART 2: FILTERING WITH A STRUCTURED EXPRESSION
// ============================================
// The same query using a filter expression (Microsoft style)
// The expression supports and/or and parentheses
GET /api/v1/products?filter=category_id eq 5 and price ge 20 and price le 100
// ============================================
// PART 3: SORTING
// ============================================
// Sort the filtered results by price, highest first
// The sort parameter is a field name; order is a direction
GET /api/v1/products?category_id=5&sort=price&order=desc
// ============================================
// PART 4: SEARCHING
// ============================================
// Search across the product's text fields for "leather"
// The server decides which fields are searchable
GET /api/v1/products?search=leather
// ============================================
// PART 5: COMPOSING ALL THREE
// ============================================
// Filter to category 5, search for "leather", sort by price
// The filter narrows the set, the search narrows further, the sort orders
GET /api/v1/products?category_id=5&search=leather&sort=price&order=desc
// ============================================
// PART 6: PAGINATION WITH FILTER AND SORT
// ============================================
// The first page of 20 results
// The response includes a next page link with the same filters and sort
GET /api/v1/products?category_id=5&sort=price&order=desc&limit=20
// Response:
// {
// "data": [...],
// "meta": { "totalItems": 120, "pageIndex": 1, "itemsPerPage": 20 },
// "links": { "next": "/api/v1/products?category_id=5&sort=price&order=desc&limit=20&cursor=abc123" }
// }
// ============================================
// PART 7: THE CURSOR FOLLOW-UP
// ============================================
// Follow the next link to get the second page
// The cursor preserves the original filters and sort
GET /api/v1/products?category_id=5&sort=price&order=desc&limit=20&cursor=abc123
The seven parts cover operator filtering, structured filter expressions, sorting, searching, composing all three, paginating with filters and sort, and following the cursor. What the session demonstrates is that the three parameter families are independent but designed to compose, and the pagination mechanism must preserve the filter and sort across pages.
Quick Reference
Filtering Syntaxes
| Style | Example | Source |
|---|---|---|
| Direct field | ?status=active | Simple APIs |
| Operator suffix | ?price_gte=20&price_lte=100 | Spree, many REST APIs |
| Filter expression | ?filter=price ge 20 and price le 100 | Microsoft Azure |
| Enum filter | ?status=active | Stripe |
Sorting Syntaxes
| Style | Example | Source |
|---|---|---|
| Sort + order | ?sort=price&order=desc | Cubecart |
| Orderby with suffix | ?orderby=price desc | Microsoft Azure |
| Order_by enum | ?order_by=period_end_date | Stripe |
Searching
| Style | Example | Behavior |
|---|---|---|
| Search parameter | ?search=leather | Server-chosen fields |
| Query parameter | ?q=leather | Server-chosen fields |
| Like match | ?value=leather | “is like” match |
Pagination Styles
| Style | Parameters | Best For |
|---|---|---|
| Offset | page, limit | Small datasets, page navigation |
| Cursor | cursor, limit | Large datasets, sequential traversal |
Best Practices
โ Do This:
// Use operator suffixes for range and comparison
?price_gte=20&price_lte=100
// Use a separate search parameter for fuzzy matching
?search=leather
// Compose filter, sort, and search in one request
?category_id=5&search=leather&sort=price&order=desc
// Use cursor pagination for large datasets
?limit=20&cursor=abc123
โ Don’t Do This:
// Use search as a substitute for a filter
?search=active // matches text, not status
// Sort on unsupported fields without an error
?sort=unindexed_field // silent slow query
// Ignore an unsupported filter and return unfiltered results
?filter=nonexistent eq 1 // the client thinks it is filtering
// Use offset pagination on a large, changing dataset
?page=5000&limit=20 // duplicates and missing items
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Duplicate items across pages | No deterministic tie-breaker | Add _id secondary sort |
| Missing items during pagination | Offset shifts when data changes | Use cursor pagination |
| Filter ignored silently | Unsupported field | Return 400 with supported fields |
| Search matches wrong field | Server’s search scope too broad | Document searchable fields |
| Sort timeout on large collection | Unindexed sort field | Restrict to indexed fields |
Real-World Examples
1. Equality filter
?status=active
2. Range filter
?price_gte=20&price_lte=100
3. Structured filter
?filter=name eq 'Milk' and price lt 2.55
4. Multi-field sort
?orderby=name desc,hireDate
5. Search
?search=leather+wallet
6. Compose filter and sort
?category_id=5&sort=price&order=desc
7. Compose all three
?category_id=5&search=leather&sort=price&order=desc
8. Offset pagination
?page=3&per_page=50
9. Cursor pagination
?limit=20&cursor=eyJhZnRlciI6NDU2
10. Sparse fields
?fields=product_id,name,price
Visual
Filter vs Search
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FILTER โ
โ client knows the field and the operator โ
โ ?status=active&price_gte=20 โ
โ โ indexable, precise, deterministic โ
โ โ
โ SEARCH โ
โ client provides text, server chooses fields โ
โ ?search=leather โ
โ โ may scan, approximate, relevance-based โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Sorting and Pagination
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FILTER narrows the set โ
โ โ โ
โ โผ โ
โ SORT orders the filtered set โ
โ โ โ
โ โผ โ
โ PAGINATION slices the ordered set โ
โ โ โ
โ โผ โ
โ Tie-breaker ensures stable slices โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Offset vs Cursor
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OFFSET โ
โ ?page=5&limit=20 โ
โ โ jump to any page โ
โ โ duplicates/missing if data changes โ
โ โ
โ CURSOR โ
โ ?limit=20&cursor=abc123 โ
โ โ follow next link โ
โ โ stable under data changes โ
โ โ no random access โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Filtering | Field-scoped predicates with operators |
| Sorting | sort + order or orderby with suffix |
| Searching | q or search, server-chosen fields |
| Composition | All three compose in one request |
| Offset pagination | page + limit, for small sets |
| Cursor pagination | cursor + limit, for large sets |
| Tie-breaker | Required for stable pagination |
| Unsupported field | Return 4xx with supported fields |
Key takeaways:
- Filtering and searching are different. A filter is a precise predicate against a known field (
status=active,price_gte=20). A search is a fuzzy match against server-chosen fields (search=leather). Confusing the two produces wrong results or unindexable queries. - Filtering needs an operator syntax. Equality alone is insufficient. Use operator suffixes (
_gte,_lte,_cont) or a structured expression (filter=price ge 20). Multiple filters combine with AND by default. - Sorting must compose with filtering and pagination. The filter narrows the set, the sort orders it, and the pagination slices it. A deterministic tie-breaker (like
_id) is required to prevent duplicates and missing items across pages. - Searching is the server’s responsibility. The client provides text, and the server decides which fields to search. The searchable fields should be documented, and the search should not be used as a substitute for a field filter.
- Offset pagination is for navigation; cursor pagination is for traversal. Offset allows jumping to a specific page but is unstable when data changes. Cursor is stable and efficient for large datasets but does not support random access.
- Unsupported fields must return an error. A filter or sort on a field that does not exist or is not indexed should return a 4xx with a message naming the supported fields. Silent no-ops are worse than errors because the client does not know the request failed.
Remember: Query parameters are the API’s control surface for collections. Filtering narrows the set, sorting orders it, searching finds text within it, and pagination slices it. Each family has its own syntax and its own semantics, and the design decisionsโoperator suffixes vs structured expressions, sort+order vs orderby, offset vs cursorโdetermine whether the API is usable at scale. The client that can filter precisely, sort deterministically, search broadly, and paginate stably is the client that can build a real interface on top of the API. The client that cannot is the client that fetches everything and filters in memory, which is the API’s failure, not the client’s.
REST, query parameters, filtering, sorting
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!