Query Parameters
OPRA provides a declarative system for defining, validating, and coercing query string parameters. Parameters are declared on the controller or operation and automatically decoded before your handler runs — no manual req.query parsing needed.
Declaring parameters
Operation level
Use .QueryParam() in the decorator chain to declare a parameter for that specific endpoint:
import { ArrayType, HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';
@HttpController({ path: 'customers' })
export class CustomersController {
@(HttpOperation.GET()
.QueryParam('status', { type: 'string', required: false })
.QueryParam('minAge', { type: 'integer', required: false })
.Response(200, { type: ArrayType(Customer) }))
async findMany(ctx: HttpContext) {
const { status, minAge } = ctx.queryParams;
// status: string | undefined
// minAge: number | undefined (already coerced from string)
}
}
Controller level
Parameters declared on the controller are available to every operation in that controller. Useful for parameters that apply across all endpoints — for example a tenant or locale:
@HttpController({ path: 'customers' })
@HttpController.QueryParam('locale', { type: 'string', required: false })
export class CustomersController {
@(HttpOperation.GET()
.Response(200, { type: ArrayType(Customer) }))
async findMany(ctx: HttpContext) {
const { locale } = ctx.queryParams; // available on every operation
}
@(HttpOperation.GET({ path: ':id' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { locale } = ctx.queryParams; // here too
}
}
Options
| Option | Type | Default | Description |
|---|---|---|---|
type | string | Type | — | OPRA type name ('string', 'integer', 'boolean', …) or a class decorated with @ComplexType(). Controls validation and coercion. |
required | boolean | false | If true, OPRA throws a BadRequestError when the parameter is absent. |
default | any | — | Value used when the parameter is absent. The default is placed in ctx.queryParams as if the client had sent it. |
isArray | boolean | false | Accepts repeated values (?tag=a&tag=b) or comma-separated lists and decodes them as an array. |
arraySeparator | string | ',' | Delimiter used to split array values within a single query string entry. |
deprecated | boolean | string | — | Marks the parameter deprecated in the API schema. Pass a string for a deprecation message. |
parser | (v: any) => any | — | Custom post-processing function applied after type coercion. |
Type coercion
Query string values are always received as strings. OPRA coerces them to the declared type automatically before placing them in ctx.queryParams. No manual conversion is needed.
@(HttpOperation.GET()
.QueryParam('limit', { type: 'integer', default: 20 })
.QueryParam('active', { type: 'boolean' }))
async list(ctx: HttpContext) {
const { limit, active } = ctx.queryParams;
// limit → number (e.g. ?limit=50 → 50)
// active → boolean (e.g. ?active=true → true)
}
Supported primitive type names: 'string', 'integer', 'number', 'bigint', 'boolean', 'date', 'datetime', 'time', 'uuid', 'object'.
Validation
OPRA validates each parameter against its declared type before calling your handler. If validation fails, the request is rejected with a 400 BadRequestError — your handler is never called.
@(HttpOperation.GET()
.QueryParam('page', { type: 'integer', required: true }))
async list(ctx: HttpContext) {
// Guaranteed: ctx.queryParams.page is an integer.
// ?page=abc → 400 Bad Request (before handler runs)
// no ?page → 400 Bad Request (required)
}
You can use any OPRA-registered type including enum types:
@(HttpOperation.GET()
.QueryParam('order', {
type: EnumType(['asc', 'desc']),
default: 'asc',
}))
async list(ctx: HttpContext) {
const order = ctx.queryParams.order; // 'asc' | 'desc'
// ?order=foo → 400 Bad Request
}
Array parameters
Declare isArray: true to accept multiple values for the same key:
@(HttpOperation.GET()
.QueryParam('tag', { type: 'string', isArray: true }))
async list(ctx: HttpContext) {
const tags = ctx.queryParams.tag; // string[]
// ?tag=a&tag=b → ['a', 'b']
// ?tag=a,b,c → ['a', 'b', 'c'] (comma-split by default)
}
Change the separator with arraySeparator:
@(HttpOperation.GET()
.QueryParam('ids', {
type: 'integer',
isArray: true,
arraySeparator: '|',
}))
async list(ctx: HttpContext) {
const ids = ctx.queryParams.ids; // number[]
// ?ids=1|2|3 → [1, 2, 3]
}
Default values
When a parameter has a default, it is placed in ctx.queryParams as if the client sent it — including coercion and validation:
@(HttpOperation.GET()
.QueryParam('limit', { type: 'integer', default: 20 })
.QueryParam('skip', { type: 'integer', default: 0 }))
async list(ctx: HttpContext) {
const { limit, skip } = ctx.queryParams;
// If client sends no limit/skip, these are 20 and 0 respectively.
}
Reading parameters from context
Decoded parameters are available on ctx.queryParams. Only parameters explicitly declared with .QueryParam() appear here — undeclared keys are ignored.
@(HttpOperation.GET()
.QueryParam('status', { type: 'string' })
.QueryParam('page', { type: 'integer', default: 1 })
.QueryParam('limit', { type: 'integer', default: 20 }))
async list(ctx: HttpContext) {
const { status, page, limit } = ctx.queryParams;
}
To access raw, undeclared query string values, parse ctx.request.url directly:
const url = new URL(ctx.request.originalUrl, 'http://x');
const rawValue = url.searchParams.get('some-undeclared-key');
ctx.queryParams only contains keys that were declared on the controller or operation. If a client sends an extra query parameter that is not declared, it is silently ignored — it will not appear in ctx.queryParams and will not cause an error.
Custom post-processing with parser
For transformations that go beyond type coercion, supply a parser function. It receives the already-coerced value and returns the final value stored in ctx.queryParams:
@(HttpOperation.GET()
.QueryParam('fields', {
type: 'string',
isArray: true,
parser: (fields: string[]) => new Set(fields),
}))
async list(ctx: HttpContext) {
const fields: Set<string> = ctx.queryParams.fields;
}