FastAPI Basics • Lesson 13

Query Parameter Models

Group related query parameters into a Pydantic model for cleaner code and better organization.

🎯 What You'll Learn

  • Group query parameters into Pydantic models
  • Use model_config with extra='forbid' to reject unexpected parameters
  • Understand the benefits of parameter models over individual parameters
  • Apply validation to groups of query parameters

Query Parameter Models

What You'll Learn

When your API endpoints accept many query parameters, listing them all individually in the function signature becomes messy and hard to maintain. FastAPI 0.115+ introduces query parameter models, letting you group related query parameters into a single Pydantic model.

By the end of this lesson, you'll understand how to:

  • Group query parameters into Pydantic models
  • Use model_config with extra="forbid" to reject unexpected parameters
  • Understand the benefits of parameter models over individual parameters
  • Apply validation to groups of query parameters

The Problem: Too Many Parameters

Consider an endpoint with many query parameters:

@app.get("/items/")
def list_items(
    limit: int = 10,
    offset: int = 0,
    order_by: str = "created_at",
    search: str | None = None,
    category: str | None = None,
    min_price: float | None = None,
    max_price: float | None = None,
):
    ...

This is hard to read, hard to reuse, and hard to maintain. If another endpoint needs the same filters, you have to copy all the parameters again.

The Solution: Query Parameter Models

With FastAPI 0.115+, you can group query parameters into a Pydantic model:

from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()


class FilterParams(BaseModel):
    model_config = {"extra": "forbid"}

    limit: int = 10
    offset: int = 0
    order_by: str = "created_at"


@app.get("/items/")
def list_items(filter_query: FilterParams = Query()):
    return {"filters": filter_query.model_dump()}

The key pieces:

  1. Define a Pydantic model with your query parameters as fields
  2. Annotate the parameter with Query() so FastAPI knows to extract values from the query string
  3. Use model_config = {"extra": "forbid"} to reject any unknown query parameters

Key Concepts

The Query() Annotation

When you declare a Pydantic model parameter in an endpoint, FastAPI normally assumes it is a JSON body. The Query() annotation tells FastAPI: "Read these fields from the query string instead."

# Without Query() - FastAPI expects a JSON body
def endpoint(params: FilterParams):  # body parameter
    ...

# With Query() - FastAPI reads from query string
def endpoint(params: FilterParams = Query()):  # query parameter
    ...

Forbidding Extra Parameters

Setting model_config = {"extra": "forbid"} causes FastAPI to return an error if the client sends unknown query parameters:

class FilterParams(BaseModel):
    model_config = {"extra": "forbid"}
    limit: int = 10

A request to /items/?limit=10&unknown=value would return a 422 Validation Error because unknown is not a recognized parameter. This prevents typos and unexpected input from silently being ignored.

Default Values

Each field in the model can have a default value. When the client omits a parameter, the default is used:

GET /items/                        -> limit=10, offset=0, order_by="created_at"
GET /items/?limit=5                -> limit=5, offset=0, order_by="created_at"
GET /items/?limit=5&order_by=name  -> limit=5, offset=0, order_by="name"

Reusing Parameter Models

One major benefit is reusability. The same model can be used across multiple endpoints:

@app.get("/items/")
def list_items(filters: FilterParams = Query()):
    return {"filters": filters.model_dump()}


@app.get("/users/")
def list_users(filters: FilterParams = Query()):
    return {"filters": filters.model_dump()}

Both endpoints now share the same parameter structure, validation, and documentation.

Adding Validation

Since the model is a standard Pydantic model, you can add field-level validation:

from pydantic import BaseModel, Field


class FilterParams(BaseModel):
    model_config = {"extra": "forbid"}

    limit: int = Field(default=10, ge=1, le=100)
    offset: int = Field(default=0, ge=0)
    order_by: str = "created_at"

This ensures limit is between 1 and 100, and offset is not negative.

Best Practices

  1. Use extra="forbid" to catch typos in query parameter names early
  2. Provide sensible defaults so callers can omit parameters they do not care about
  3. Reuse models across endpoints that share the same filter logic
  4. Add Field validation to constrain values at the model level
  5. Keep models focused -- create separate models for different groups of parameters rather than one large model

What's Next?

You now know how to group query parameters into Pydantic models. This same pattern extends to cookies, headers, and form data, which you will explore in the following lessons.

💡 Hint

Use a Pydantic model with model_config = {'extra': 'forbid'} and annotate it with Query() in your endpoint.

Ready to Practice?

Now that you understand the theory, let's put it into practice with hands-on coding!

Start Interactive Lesson