Search Topics
Search across all FastAPI topics
Path Parameters
Core ConceptPath parameters let you capture dynamic values from URL segments. FastAPI validates and converts them automatically using Python type hints.
You define @app.get("/items/{item_id}") with item_id: int. A user hits /items/abc. Instead of a nice error page, they get a raw 422 JSON dump.
GET /items/abc → 422 Unprocessable Entity
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
}Question
Ever seen a 422 and had no idea why? Here's the thing: FastAPI didn't crash. It actually protected you. You said item_id: int, and someone sent "abc." FastAPI caught that before your code ever ran. The 422 is a feature, not a bug.
How Path Parameter Validation Works
When a request comes in, FastAPI extracts the dynamic segment from the URL, tries to convert it to your declared type, and either hands it to your function or rejects the request. Your code never sees bad data.
URL arrives
/items/abc
Extract segment
"abc"
Convert to int
int('abc') fails!
422 Response
Request rejected
URL arrives
/items/42
Extract segment
"42"
Convert to int
int('42') = 42
Run handler
read_item(42)
Path parameter basics
What you just learned
Path parameters are extracted from URL segments and validated automatically
Type hints drive the validation — int rejects non-numeric strings
Your function only runs if all validations pass
Basic Path Parameters
Declare path parameters using curly braces in the path string. The function parameter name must match exactly, and the type hint tells FastAPI how to validate.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
# GET /items/42 → {"item_id": 42} ✓ valid int
# GET /items/foo → 422 Validation Error ✗ not an intMultiple Path Parameters
You can have as many path parameters as you need. Each one maps to a function parameter by name. And you can mix them with query parameters too — FastAPI figures out which is which.
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
user_id: int, # ← from path
item_id: int, # ← from path
q: str | None = None, # ← from query string
short: bool = False, # ← from query string
):
return {"user_id": user_id, "item_id": item_id, "q": q}
# GET /users/5/items/42?q=search
# → {"user_id": 5, "item_id": 42, "q": "search"}Parameter declaration
What you just learned
Curly braces in the path string define parameter slots
Function parameter names must match the path parameter names
Path params and query params can coexist in the same function
Type Validation Beyond int and str
FastAPI doesn't stop at basic types. UUID, date, and other complex types are validated automatically. This means you can reject malformed IDs at the routing level, before your database ever sees them.
from uuid import UUID
from datetime import date
@app.get("/users/{user_id}")
async def read_user(user_id: UUID):
return {"user_id": user_id}
# GET /users/550e8400-e29b-41d4-a716-446655440000 → ✓
# GET /users/not-a-uuid → 422 Validation Error
@app.get("/reports/{report_date}")
async def read_report(report_date: date):
return {"date": report_date}
# GET /reports/2024-01-15 → ✓
# GET /reports/yesterday → 422 Validation ErrorTry It: Type Validation
Type a value and pick a type to see if FastAPI would accept it or return a 422 validation error.
Go Deeper: Path() Validation with Annotated
Want to say "item_id must be a positive integer under 10,000"? That's what Path() is for. Pair it with Annotated for the cleanest syntax.
from fastapi import FastAPI, Path
from typing import Annotated
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: Annotated[int, Path(
title="Item ID",
description="The unique identifier for the item",
ge=1, # greater than or equal to 1
le=10000, # less than or equal to 10000
)],
):
return {"item_id": item_id}
# GET /items/0 → 422 (ge=1 violated)
# GET /items/5 → ✓
# GET /items/99999 → 422 (le=10000 violated)
# Path + Query together with Annotated:
@app.get("/items/{item_id}")
async def read_item(
item_id: Annotated[int, Path(ge=1)],
q: Annotated[str | None, Query(max_length=50)] = None,
):
return {"item_id": item_id, "q": q}Enum Path Parameters
Sometimes you don't want any string — you want one of three specific values. Python Enums let you lock down the allowed options, and Swagger UI will show a dropdown.
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model": model_name, "message": "Deep Learning FTW!"}
return {"model": model_name}
# GET /models/alexnet → ✓
# GET /models/vgg → 422 (not in enum)
# Swagger UI shows a dropdown with the valid optionsThe Route Order Trap
You define /items/{item_id} before /items/latest. Someone visits /items/latest and gets a 422 error. The "latest" endpoint never runs.
# ❌ Wrong order — "latest" gets captured as item_id
@app.get("/items/{item_id}") # This catches everything!
async def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/items/latest") # Never reached
async def read_latest():
return {"item": "latest one"}Watch out
This is one of the most common FastAPI gotchas. If you ever get a 422 on a route that shouldn't have parameters, check your route order first. Fixed paths always need to come before dynamic ones.
Go Deeper: Path Parameters with Slashes
Need to capture a file path like home/user/data.csv? Normally slashes split the URL into segments. The :path converter tells FastAPI to capture everything, slashes included.
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
# GET /files/home/user/data.csv
# → {"file_path": "home/user/data.csv"}
# Without :path, slashes would be treated as URL separators
# and you'd get a 404Advanced path parameters
What you just learned
Path() adds constraints like ge, le, min_length to path parameters
Enums restrict parameters to a fixed set of allowed values
Route order matters — fixed paths must come before dynamic ones
The :path converter captures values that contain slashes
Think about it...
You have /items/latest and /items/{item_id}. You defined {item_id} first. What happens when someone visits /items/latest?
Hint: FastAPI checks routes in definition order, not by specificity.
Key Points
Auto Validation
Type hints drive automatic validation — int, str, UUID, date
Path()
Add ge, le, title, description for richer validation
Annotated
Annotated[int, Path(ge=1)] — the recommended pattern
Enum Constraints
Python Enums restrict to predefined values with dropdown in docs
Order Matters
Fixed paths (/items/latest) before dynamic (/items/{id})
:path Converter
Capture values with slashes using {file_path:path}