PARAM
Also known as
param, parameter (with or without @ prefix)
Rename and optionally retype individual endpoint parameters. This provides better API ergonomics by replacing positional parameter names ($1, $2) or internal parameter names (_old_name) with cleaner, user-facing names.
Works on all endpoint types — functions, procedures, CRUD, and SQL file endpoints.
TIP
The @param keyword is shared with the PARAMETER_HASH annotation (@param X is hash of Y). Both forms coexist without ambiguity — the parser distinguishes them by the presence of hash of in the annotation.
Syntax
code
@param <old_name> <new_name>
@param <old_name> <new_name> <type>
@param <old_name> is <new_name>
@param <old_name> is <new_name> <type>
@param <old_name> default <value>
@param <old_name> <new_name> default <value>
@param <old_name> <new_name> <type> default <value>
@param <old_name> is <new_name> default <value>
@param <old_name> is <new_name> <type> default <value>
# `=` can be used instead of `default` in all forms above:
@param <old_name> = <value>
@param <old_name> <new_name> = <value>
@param <old_name> <new_name> <type> = <value>
@param <old_name> is <new_name> = <value>
@param <old_name> is <new_name> <type> = <value>old_name: The original parameter name (e.g.,$1,$2, or_old_name)new_name: The new parameter name for the HTTP API. Used as-is — no name conversion is applied. If you write@param $1 authorId, the HTTP parameter name is exactlyauthorId, notauthor_idorauthor-id.type: Optional PostgreSQL type override (e.g.,integer,text,boolean)
Both @param and @parameter (long form) are supported.
Examples
Rename Positional Parameters (SQL Files)
SQL files use PostgreSQL positional parameters ($1, $2, ...) which aren't user-friendly as HTTP parameter names. Use @param to give them meaningful names:
sql
-- sql/get_reports.sql
-- HTTP GET
-- @param $1 from_date
-- @param $2 to_date
select id, title, created_at
from reports
where created_at between $1 and $2;Without rename: GET /api/get-reports?$1=2024-01-01&$2=2024-12-31
With rename: GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31
Rename with Type Override
When parameter types can't be inferred correctly, or when you need to override the inferred type:
sql
-- @param $1 user_id integer
-- @param $2 active boolean
select * from users where id = $1 and active = $2;Rename Function Parameters
Works on function and procedure parameters too — useful when internal naming conventions (like _ prefixes) shouldn't leak into the API:
sql
create function get_user_profile(_user_id int, _include_stats boolean)
returns json
language sql
begin atomic;
select json_build_object('id', id, 'name', name) from users where id = _user_id;
end;
comment on function get_user_profile(int, boolean) is '
HTTP GET
@param _user_id user_id
@param _include_stats include_stats
';Without rename: GET /api/get-user-profile?_user_id=1&_include_stats=true
With rename: GET /api/get-user-profile?user_id=1&include_stats=true
"is" Style Syntax
The is keyword is optional and provides consistency with the existing @param X is hash of Y style:
sql
-- These are equivalent:
-- @param $1 user_id
-- @param $1 is user_id
-- With type override:
-- @param $1 user_id integer
-- @param $1 is user_id integerClaim Mapping with Renamed Parameters
Renamed parameters work with user_parameters claim mapping. When you rename a positional parameter to a claim-mapped name (like _user_id or _user_name), the parameter is automatically filled from the authenticated user's claims — just like it would be for a native function parameter.
sql
-- sql/get_my_profile.sql
-- @authorize
-- @user_parameters
-- @param $1 _user_id
-- @param $2 _user_name
select $1 as user_id, $2 as user_name;GET /api/get-my-profile (authenticated as user123) → [{"userId": "user123", "userName": "user"}]
The parameters are auto-filled from claims — the client doesn't need to send them. This is especially useful for SQL file endpoints where positional parameters ($1, $2) have no inherent name for claim matching.
Default Values (SQL File Parameters)
SQL file parameters can have default values via @param. When a parameter with a default is not provided in the request, the default value is bound instead of returning 404.
This is essential for SQL files because positional parameters ($1, $2) must always be bound — unlike PostgreSQL functions where the engine applies its own defaults.
Syntax
sql
-- Separate annotations (rename first, then set default):
-- @param $1 user_id
-- @param user_id default null
-- Combined rename + default on a single line:
-- @param $1 user_id default null
-- Default without rename:
-- @param $1 default 'fallback'
-- Various value types:
-- @param $1 status default 'active' -- text (single-quoted)
-- @param $1 amount default 42 -- number
-- @param $1 enabled default true -- boolean
-- @param $1 filter default null -- SQL NULL (unquoted)
-- @param $1 tag default 'null' -- literal text "null" (quoted)
-- @param $1 val default -- no value = NULLInline comments after the value are ignored. The parser consumes only the value token (or quoted string) and stops.
sql
-- `=` can be used instead of `default` in all forms:
-- @param $1 user_id = null
-- @param $1 user_id integer = 42
-- @param $1 is greeting = 'hey'
-- @param my_name = 'hello'Value Parsing Rules (SQL Conventions)
- Unquoted
null(case-insensitive) →DBNull.Value - Single-quoted
'text value'→ string literal (supports multi-word) - Unquoted value → raw string (Npgsql handles type conversion via
NpgsqlDbType)
Example
User identity endpoint with claim-filled parameters that fall back to NULL:
sql
/* HTTP GET
@authorize
@user_parameters
@param $1 _user_id default null
@param $2 _username default null
@param $3 _email default null
*/
select $1 as user_id, $2 as username, $3 as email;When authenticated, claims fill the parameters automatically. The defaults ensure the parameters are always bindable.
Effects on Generated Output
- TsClient: Parameters with defaults get
?suffix in TypeScript interfaces (optional) - OpenAPI: Parameters with defaults are marked
required: false
Rename Validation
Parameter names are validated when renaming. Invalid renames are rejected with a warning log instead of silently creating broken endpoints.
Rules:
- Must be a valid PostgreSQL identifier: starts with letter or
_, followed by letters, digits,_, or$ - Positional parameters (
$1,$2) are allowed
sql
-- Valid:
-- @param $1 user_id ✓
-- @param $1 _val$1 ✓
-- Rejected (with warning log):
-- @param $1 1bad ✗ starts with digit
-- @param $1 my-param ✗ invalid character (hyphen)Composite Type Parameters (SQL Files)
When a parameter type is a known composite type, the parameter is treated as a single text value. The SQL is never rewritten — it stays exactly as written.
HTTP custom types (auto-filled from HTTP calls):
sql
-- @param $1 _response example_9.exchange_rate_api
select ($1::example_9.exchange_rate_api).body;The framework makes the HTTP call and passes the response as a composite text value automatically.
Client-sent composite types:
sql
-- @param $1 data my_composite_type
select ($1::my_composite_type).field1, ($1::my_composite_type).field2;The client sends the value as PostgreSQL composite text format: ?data=("val1","val2").
If the type in @param is not a recognized PostgreSQL type or composite type, a warning is logged and the parameter keeps its original type from Describe.
Behavior
- The rename applies to HTTP parameter names only — the SQL still uses the original parameter name or positional reference
- Type overrides affect how the HTTP parameter value is parsed and converted before being sent to PostgreSQL
- For multi-command SQL files, parameter types are merged across all statements — use type override to resolve conflicts
- The parameter must exist in the endpoint's parameter list; otherwise a warning is logged and the rename is skipped
- Renamed parameters participate in claim mapping — renaming
$1to_user_idenables automaticuser_parametersfilling from thename_identifierclaim
Related
- Comment Annotations Guide - How annotations work
- PARAMETER_HASH - Hash one parameter using another (also uses
@param) - REQUEST_PARAM_TYPE - Control query string vs body parameters