DocumentsKubb's Space
OpenAPI Query Parameter Serialization
OpenAPI Query Parameter Serialization
Type
Topic
Status
Published
Created
Jul 8, 2026
Updated
Jul 8, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

OpenAPI Query Parameter Serialization#

Kubb preserves OpenAPI style, explode, and additionalProperties metadata through its entire pipeline — from spec parsing into a spec-neutral AST, through code generation, to runtime serializers in generated client code. This end-to-end flow ensures generated clients serialize array and object parameters exactly as the OpenAPI spec declares.

Pipeline Overview#

OpenAPI Spec
    │ style / explode / additionalProperties
adapter-oas: parseParameter() ← reads raw spec fields
    │ ParameterNode { style?, explode? }
AST: ParameterNode ← spec-neutral representation
Client generator: buildStyles() ← emits styles literal per operation
    │ styles: { query: { param: { style, explode } } }
Generated client function ← carries styles at call site
Runtime: defaultQuerySerializer() ← applies style/explode at request time

1. AST: ParameterNode#

ParameterNode in @kubb/ast carries two optional serialization fields:

  • style?: ParameterStyle — one of matrix | label | form | simple | spaceDelimited | pipeDelimited | deepObject
  • explode?: boolean — controls array/object expansion

Both fields are absent when the spec omits them, intentionally leaving consumers to apply per-location defaults rather than hard-coding OpenAPI assumptions into the AST.

2. Parsing: adapter-oas#

parseParameter() in @kubb/adapter-oas reads style and explode from the raw parameter object and sets them on the ParameterNode only when the spec provides them:

const style = param['style'] as ast.ParameterStyle | undefined
const explode = param['explode'] as boolean | undefined
// ...
...(style !== undefined ? { style } : {}),
...(explode !== undefined ? { explode } : {}),

This was introduced in PR #3676 specifically to preserve per-parameter serialization metadata for downstream generators.

3. Code Generation: buildStyles()#

buildStyles() in internals/client converts the ParameterNode list on an operation into a styles object literal that is embedded in each generated request function:

  • Path and query parameters emit style (when present) plus explode.
  • Header and cookie parameters omit style (those locations have fixed styles — simple and form respectively) and only emit explode.
  • Parameters carrying neither style nor explode are omitted entirely, so operations without serialization metadata produce no styles argument and the runtime falls back to its defaults.

Example generated output for GET /pet/findByStatus (where status has explode: true):

return request({ method: 'GET', url: '/pet/findByStatus', styles: { query: { status: { explode: true } } }, ...config })

4. Runtime Serializers#

The serializers are emitted as a shared module (.kubb/serializers.ts) alongside the generated clients.

Query: defaultQuerySerializer routes each parameter through serializeStyledQueryParam when its entry is found in the styles.query map, or through serializeDefaultQueryParam (arrays repeat as separate keys, objects use deepObject) otherwise.

Style behaviors for arrays :

Styleexplode: trueexplode: false
form (default)id=3&id=4id=3,4
spaceDelimitedid=3&id=4id=3%204%205
pipeDelimitedid=3&id=4id=3|4|5
deepObjecta[b]=1

Path: defaultPathSerializer defaults to simple style with explode: false and handles label (.-prefix) and matrix (;name=-prefix) styles for arrays and objects.

Header: applyHeaderStyles applies simple style using the explode flag only — header values are not URL-encoded.

Cookie: serializeCookies uses form style; without explode, arrays join with commas; with explode, each value gets its own name=value pair joined by ; .

5. additionalProperties and explode: true Flattening#

A separate code-gen concern: when a query parameter has style: form, explode: true, and its schema is an object with only additionalProperties (no explicit properties), the generated TypeScript type should be a flat index signature rather than a nested property.

PR #2195 fixed this in packages/oas/src/Oas.ts (getParametersSchema): such parameters are detected and their additionalProperties is promoted to the root-level query params schema, producing { [key: string]: string } instead of { customFields?: { [key: string]: string } }.

Key Source Files#

FilePurpose
packages/ast/src/nodes/parameter.tsParameterNode type with style / explode
packages/adapter-oas/src/parser.tsReads style/explode from OAS spec
internals/client/src/builders/styles.tsEmits per-operation styles literal
packages/plugin-fetch/templates/serializers.tsRuntime serializer implementations
tests/3.0.x/__snapshots__/pluginFetch/default/clients/findPetsByStatus.tsGenerated client snapshot with styles
tests/3.0.x/__snapshots__/pluginFetch/default/.kubb/serializers.tsGenerated serializers snapshot