Search Topics
Search across all FastAPI topics
Query Parameters
Core ConceptQuery parameters are the key-value pairs after the ? in a URL. FastAPI handles them automatically through function parameters that are not part of the path.
You add limit: int as a query parameter. A user forgets to include it: GET /items. Boom — 422. Your endpoint is broken for every user who doesn't know about mandatory query params.
GET /items → 422 Unprocessable Entity
{
"detail": [
{
"type": "missing",
"loc": ["query", "limit"],
"msg": "Field required",
"input": null
}
]
}Question
Why should a simple list endpoint require the caller to know about a limit parameter? The answer: it shouldn't. The fix is a single = 10 after the parameter declaration. That tiny default value is the difference between a friendly API and a frustrating one.
How FastAPI Decides: Required vs. Optional
FastAPI looks at one thing to decide if a query parameter is required: does it have a default value?
Parameter declared
limit: int
Has default?
No default value
REQUIRED
422 if missing
Parameter declared
limit: int = 10
Has default?
Default is 10
OPTIONAL
Uses 10 if missing
Required vs optional
What you just learned
No default value = required query parameter (422 if missing)
With a default value = optional (uses the default if not provided)
This one rule prevents most query parameter 422 errors
Basic Query Parameters
Any function parameter that's not in the path string becomes a query parameter automatically. You don't need to declare it anywhere special — FastAPI just knows.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items")
async def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
# GET /items → {"skip": 0, "limit": 10} (defaults)
# GET /items?skip=20 → {"skip": 20, "limit": 10}
# GET /items?skip=20&limit=50 → {"skip": 20, "limit": 50}Both skip and limit have defaults, so calling GET /items with no params works perfectly.
Required vs Optional — In Practice
Here's how the three flavors of query parameters look in real code. The search query is required (no default), while category and language are optional.
@app.get("/search")
async def search(
q: str, # Required — no default
category: str = "all", # Optional — has default
lang: str | None = None, # Optional — explicit None
):
return {"q": q, "category": category, "lang": lang}
# GET /search?q=fastapi → ✓ (category="all", lang=None)
# GET /search → 422 "q is required"
# GET /search?q=fastapi&lang=en → ✓Query parameter patterns
What you just learned
Non-path function parameters automatically become query params
Required params (no default) return 422 when missing
str = 'default' always gives you a string; str | None = None might give you None
Adding Validation with Query()
Defaults are great, but what if you need to say "the search query must be at least 3 characters and no more than 50"? That's where Query() comes in. Pair it with Annotated for clean, readable code.
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items")
async def list_items(
q: Annotated[
str | None,
Query(
min_length=3,
max_length=50,
pattern="^[a-zA-Z0-9 ]+$",
title="Search Query",
description="Search for items by name",
examples=["fastapi", "python web"],
),
] = None,
skip: Annotated[int, Query(ge=0, description="Items to skip")] = 0,
limit: Annotated[int, Query(ge=1, le=100)] = 10,
):
return {"q": q, "skip": skip, "limit": limit}
# GET /items?q=ab → 422 (min_length=3 violated)
# GET /items?q=fastapi → ✓
# GET /items?limit=200 → 422 (le=100 violated)List Query Parameters
Want to accept multiple tags in a single request? Just type your parameter as a list. The client repeats the key in the URL, and FastAPI collects them all.
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items")
async def filter_items(
tag: Annotated[list[str], Query()] = [],
):
return {"tags": tag}
# GET /items?tag=python&tag=fastapi&tag=async
# → {"tags": ["python", "fastapi", "async"]}
# With validation:
@app.get("/search")
async def search(
category: Annotated[
list[str],
Query(min_length=1, description="Filter categories"),
] = ["general"],
):
return {"categories": category}Query validation
What you just learned
Query() adds min/max length, regex patterns, and numeric constraints
Annotated[type, Query(...)] is the modern, recommended syntax
list[str] collects repeated keys into a Python list
Try It: Query String Builder
Add, remove, and edit query parameters to see how the URL builds up and what FastAPI receives. Try repeating a key to see list detection.
Go Deeper: Alias & Deprecated Params
Sometimes your URL needs a param name that isn't a valid Python variable (like item-query). And sometimes you need to keep an old parameter working while nudging users to the new one.
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items")
async def list_items(
# URL uses "item-query" but Python uses "item_query"
item_query: Annotated[
str | None,
Query(alias="item-query"),
] = None,
# Marked deprecated — shows warning in docs
old_filter: Annotated[
str | None,
Query(
deprecated=True,
description="Use 'q' instead. Will be removed in v2.",
),
] = None,
):
return {"q": item_query}
# GET /items?item-query=phone → item_query = "phone"
# The deprecated param still works but shows a warning in /docsBoolean Query Parameters
Insight
FastAPI is surprisingly flexible with booleans. "true", "1", "yes", "on" — they all become True. This means your users don't have to remember the exact format.
@app.get("/items")
async def list_items(
short: bool = False,
include_deleted: bool = False,
):
return {"short": short, "include_deleted": include_deleted}
# All of these set short=True:
# GET /items?short=true
# GET /items?short=1
# GET /items?short=yes
# GET /items?short=on
# GET /items?short=TrueThink about it...
What's the difference between limit: int = 10 and limit: int | None = None as query parameter declarations?
Hint: Think about what value your code receives when the parameter is missing from the URL.
Key Points
Auto Detection
Non-path function params become query params automatically
Required vs Optional
No default = required (422 if missing). Default = optional.
Annotated + Query()
Add min/max length, pattern, ge/le, title, description
List Parameters
list[str] collects repeated keys: ?tag=a&tag=b → ["a", "b"]
Alias
Map URL param names to valid Python variables
Deprecated
Mark params for removal — still works but warns in docs