Query parameters

FastAPI · Essentials · 8 min

Filtering, pagination, sorting and search options all travel as query parameters.

Any function parameter that isn't in the path and has a simple type (int, str, bool, float, enum...) is read from the query string, the ?key=value&key2=value2 part of the URL. Types work exactly as for path parameters: values arrive as strings and are converted and validated.

Whether a query parameter is required depends only on its default:

  • limit: int = 10 is optional; when it's missing you get 10.
  • q: str | None = None is optional; when it's missing you get None, so test with if q is not None.
  • status: str with no default is required; leaving it out gives a 422.

Booleans are deliberately forgiving: true, 1, yes and on mean True; false, 0, no and off mean False (case-insensitive). Anything else is a 422.

Path and query parameters mix freely, and the order of function parameters doesn't matter: FastAPI identifies each one by name. A name that appears in the path template is a path parameter; any other simple-typed parameter is a query parameter.

Lists are the one surprise. To accept a repeated key such as ?tag=a&tag=b, write tag: Annotated[list[str], Query()] = []. Without Query(), FastAPI would assume a list parameter is part of a JSON request body. Query() also adds constraints like max_length, which the validation lesson covers.

Keep query parameters for things that select or shape a response (filters, pagination, sort order), not for data you're creating; that belongs in the request body.

Example

from typing import Annotated
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/users/{user_id}/orders")
def list_orders(
    user_id: int,                 # path parameter
    status: str,                  # required query parameter
    skip: int = 0,                # optional, defaults to 0
    limit: int = 10,
    paid: bool | None = None,     # optional, None when absent
    tag: Annotated[list[str], Query()] = [],   # ?tag=a&tag=b
):
    return {"user": user_id, "status": status, "page": [skip, limit],
            "paid": paid, "tags": tag}

c = TestClient(app)
print(c.get("/users/1/orders?status=open&paid=yes&tag=x&tag=y").json())
# {'user': 1, 'status': 'open', 'page': [0, 10], 'paid': True, 'tags': ['x', 'y']}
print(c.get("/users/1/orders").status_code)   # 422: status is required

Remember

  • Simple-typed parameters not in the path come from the query string
  • No default = required; a default (often None) = optional
  • bool accepts true/false, 1/0, yes/no, on/off
  • Repeated keys need Annotated[list[str], Query()]

Watch out for

  • Writing q: str = None: it works but lies about the type; use str | None = None.

Check yourself

  1. For def search(q: str, page: int = 1), what does GET /search?page=2 return?
  2. GET /items?active=off reaches active: bool = True. What value does active have?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: fastapi.tiangolo.com/tutorial/query-params/