Path parameters are the dynamic chunks of a URL. In /users/42, the 42 is a path parameter. FastAPI captures them with curly braces in the path string and matches them to function arguments by name.
The basic shape
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "type": type(user_id).__name__}
The name in {user_id} must match the function parameter name. The type hint int does two things:
- FastAPI converts the string from the URL into an int before calling the function.
- If the conversion fails, FastAPI returns 422 with a clear error — we never see the bad value.
curl http://localhost:8000/users/42
# {"user_id":42,"type":"int"}
curl http://localhost:8000/users/abc
# 422 - {"detail":[{"type":"int_parsing","loc":["path","user_id"],...}]}
Supported types
The usual suspects work out of the box:
int—/items/5float—/prices/19.99str—/slug/hello-world(the default; matches any non-slash text)bool—/feature/true(accepts “true”, “false”, “1”, “0”, “yes”, “no”)UUID—/orders/123e4567-e89b-12d3-a456-426614174000
Validating with Path()
For deeper validation (min/max, regex, etc.) we use the Path() helper:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(
...,
title="Item ID",
ge=1, # greater than or equal to 1
le=10000, # less than or equal to 10000
description="The numeric ID of the item",
),
):
return {"item_id": item_id}
The ... (Ellipsis) means “required, no default.” Available validators:
ge,gt,le,lt— numeric boundsmin_length,max_length— string boundspattern— regex constraint
@app.get("/products/{sku}")
async def get_product(sku: str = Path(..., pattern=r"^SKU-\d{6}$")):
return {"sku": sku}
If someone hits /products/banana they get a 422. Only SKU-123456 style values pass through.
Predefined values with Enum
If we want a path param to only accept a fixed set of strings, use a str Enum. FastAPI shows a dropdown in /docs:
from enum import Enum
class OrderStatus(str, Enum):
pending = "pending"
shipped = "shipped"
delivered = "delivered"
@app.get("/orders/status/{status}")
async def list_by_status(status: OrderStatus):
if status is OrderStatus.shipped:
return {"message": "Showing shipped orders"}
return {"message": f"Showing {status.value} orders"}
Important: the Enum must inherit from str (or int) so OpenAPI knows the underlying primitive. Try any value outside the enum and we get 422.
order_id = 42 (from {order_id} in route)limit = 10 (after the ?)Path containing slashes
By default str path params stop at the next /. If we genuinely need to match a path like /files/foo/bar/baz.txt, we tell FastAPI with :path:
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
Now file_path captures everything after /files/, slashes included. Useful for proxying file paths or wiki-style URLs.
Multiple path params
Just add more {} and matching args:
@app.get("/users/{user_id}/orders/{order_id}")
async def get_user_order(user_id: int, order_id: int):
return {"user_id": user_id, "order_id": order_id}
Order in the function signature doesn’t matter — FastAPI matches by name.
Common interview gotcha
If asked “what if the path param doesn’t match the type?” — FastAPI returns 422 with a structured error before our handler ever runs. We don’t have to write a try/except. That’s the whole pitch: validation happens at the framework boundary.