FastAPI Path and Query Parameters

Type-hinted path parameters, optional query parameters, and adding validation constraints.

Path parameters

A path parameter is declared exactly like a Python function parameter — its type hint is what tells FastAPI how to parse and validate it:

Python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id, "type": str(type(user_id))}

Requesting /users/42 returns {"user_id": 42, "type": "<class 'int'>"} — FastAPI converted the URL segment (always text on the wire) into a real Python int before your function ever ran. Requesting /users/abc never reaches your code at all: FastAPI responds with a 422 Unprocessable Entity and a JSON body explaining exactly which field failed validation and why.

Query parameters

Any function parameter that isn't part of the path is automatically treated as a query parameter — read from the ?key=value part of the URL:

Python
@app.get("/items/")
def list_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

GET /items/?skip=20&limit=5 returns {"skip": 20, "limit": 5}. Giving a parameter a default value (= 0, = 10) makes it optional — omit it from the URL and the default is used. Mark a query parameter as genuinely optional with no default of its own using | None:

Python
@app.get("/search")
def search(q: str | None = None):
    if q is None:
        return {"results": "no query provided"}
    return {"results": f"searching for '{q}'"}

Combining path and query parameters

A single endpoint routinely mixes both kinds of parameters — required, structural information in the path, and optional, refining information in the query string:

Python
@app.get("/users/{user_id}/orders")
def list_user_orders(
    user_id: int,
    status: str | None = None,
    limit: int = 20,
):
    orders = fetch_orders(user_id, status=status)  # your own lookup logic
    return {"user_id": user_id, "status_filter": status, "orders": orders[:limit]}

GET /users/7/orders?status=shipped&limit=5 resolves to user_id=7, status="shipped", limit=5 — FastAPI matches each name in the URL against the function's parameter names, pulling structural values from the path and everything else from the query string, with no configuration needed to tell it which is which.

Adding validation constraints

Query and Path let you attach extra constraints (and documentation) beyond the bare type:

Python
from typing import Annotated
from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(
    item_id: Annotated[int, Path(gt=0, description="The ID of the item to fetch")],
    q: Annotated[str | None, Query(max_length=50)] = None,
):
    return {"item_id": item_id, "q": q}

Path(gt=0, ...) means "must be an integer greater than zero" — a request for /items/0 or /items/-3 is rejected automatically, with a 422 response, before your function body runs. Annotated[Type, ...] is the current recommended style for attaching this kind of metadata to a parameter; older FastAPI code (and plenty of code still in the wild) instead writes item_id: int = Path(gt=0), which works identically.

Common mistakes

  • Assuming an unannotated function parameter with no default is a query parameter — if its name also appears in the route's {...} path template, FastAPI treats it as a path parameter regardless of order.
  • Using a mutable or overly loose type (like a bare str for something that should be an int) and relying on manual int(...) conversion + your own error handling, duplicating validation FastAPI already does for free.
  • Forgetting that a path parameter is always required — there's no way to make part of the URL path itself optional; an optional value belongs in the query string instead.

Interview questions

Q: How does FastAPI decide whether a parameter is a path parameter or a query parameter? It matches the endpoint function's parameter names against the placeholders ({...}) declared in the route's path string. Any parameter name found in the path template is a path parameter; every other parameter is treated as a query parameter automatically, with no separate declaration required to tell them apart.

Q: What happens when a request fails path or query parameter validation? FastAPI returns a 422 Unprocessable Entity response with a JSON body describing exactly which field failed and why (e.g., "value is not a valid integer"), and your endpoint function never executes at all. This is enforced purely from the type hints (and any Path/Query constraints) you declared — no manual validation code is required for the common cases.