Skip to main content

Path Parameters

Path parameters are variable segments of the URL captured by name. FastAPI uses your Python type hints to validate and coerce them automatically — an invalid type triggers a 422 before your handler runs.

Learning Focus

By the end of this lesson you can: declare typed path parameters, add metadata and constraints with Path, validate enums in paths, and handle multiple path parameters in a single route.

Declaring a Path Parameter

app/routers/items.py
from fastapi import APIRouter

router = APIRouter(prefix="/items", tags=["items"])

@router.get("/{item_id}")
async def get_item(item_id: int) -> dict:
return {"item_id": item_id}

The {item_id} segment in the path is captured and passed to the function. FastAPI converts it to int — if the request sends /items/abc, FastAPI returns:

{
"detail": [{"type": "int_parsing", "loc": ["path", "item_id"], "msg": "..."}]
}

No handler code runs. Validation is free.

Type Coercion in Paths

Type hintAccepted path segmentsRejected
int123, 0, -1abc, 1.5
float1.5, 3, -0.1abc
strAny text
booltrue, false, 1, 0yes, no
uuid.UUIDValid UUID4 stringMalformed UUID
app/routers/resources.py
import uuid
from fastapi import APIRouter

router = APIRouter()

@router.get("/users/{user_id}")
async def get_user(user_id: uuid.UUID) -> dict:
return {"user_id": str(user_id)}

@router.get("/posts/{slug}")
async def get_post(slug: str) -> dict:
return {"slug": slug}

Path with Annotated and Path

Use Annotated and Path to add validation constraints and OpenAPI metadata:

app/routers/items.py
from typing import Annotated
from fastapi import APIRouter, Path

router = APIRouter(prefix="/items", tags=["items"])

@router.get("/{item_id}")
async def get_item(
item_id: Annotated[
int,
Path(
title="The item ID",
description="Must be a positive integer",
ge=1, # >= 1
le=1_000_000, # <= 1,000,000
example=42,
),
],
) -> dict:
return {"item_id": item_id}

Available Path constraints:

ConstraintTypeMeaning
genumericGreater than or equal
gtnumericGreater than
lenumericLess than or equal
ltnumericLess than
min_lengthstrMinimum string length
max_lengthstrMaximum string length
patternstrRegex pattern

Enum Path Parameters

When a path segment must be one of a fixed set of values, use an Enum:

app/routers/reports.py
from enum import Enum
from fastapi import APIRouter

class ReportFormat(str, Enum):
json = "json"
csv = "csv"
xml = "xml"

router = APIRouter(prefix="/reports", tags=["reports"])

@router.get("/{report_id}/export/{format}")
async def export_report(report_id: int, format: ReportFormat) -> dict:
return {"report_id": report_id, "format": format.value}

FastAPI automatically documents the enum values in OpenAPI — users can only send json, csv, or xml. Any other value returns a 422.

Multiple Path Parameters

A single route can have several path parameters:

app/routers/organizations.py
from fastapi import APIRouter, Path
from typing import Annotated

router = APIRouter(prefix="/orgs", tags=["organizations"])

@router.get("/{org_id}/repos/{repo_id}/issues/{issue_number}")
async def get_issue(
org_id: Annotated[str, Path(min_length=1, max_length=50)],
repo_id: Annotated[str, Path(min_length=1, max_length=100)],
issue_number: Annotated[int, Path(ge=1)],
) -> dict:
return {
"org": org_id,
"repo": repo_id,
"issue": issue_number,
}

Path Parameters with Slash (/) in Value

By default {param} does not match /. To allow a file path value:

app/routers/files.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/files/{file_path:path}")
async def read_file(file_path: str) -> dict:
# file_path captures "a/b/c.txt" from /files/a/b/c.txt
return {"path": file_path}
warning

:path converters bypass path segment validation. Always sanitize the value to prevent path traversal attacks.

Common Pitfalls

PitfallCause / SymptomFix
422 when passing a string that looks like intUsing int type hint on non-numeric IDsUse str for slug/UUID identifiers, not int
Route matching /me as UUID/me declared after /{user_id: UUID}Put /me before /{user_id}
Negative IDs acceptedNo ge=1 constraintUse Path(ge=1) with Annotated
Enum values not in docsDidn't inherit from strAlways inherit from both str and Enum: class X(str, Enum)
Path traversal via :pathUnvalidated file pathsStrip leading /, validate against allowed directories

Hands-On Practice

app/routers/catalog.py
from enum import Enum
from typing import Annotated
from fastapi import APIRouter, Path, HTTPException

class Category(str, Enum):
electronics = "electronics"
clothing = "clothing"
books = "books"

router = APIRouter(prefix="/catalog", tags=["catalog"])

_items = {
1: {"name": "Laptop", "category": "electronics"},
2: {"name": "T-Shirt", "category": "clothing"},
3: {"name": "Python Cookbook", "category": "books"},
}

@router.get("/{category}")
async def list_by_category(category: Category) -> list[dict]:
return [
{"id": k, **v}
for k, v in _items.items()
if v["category"] == category.value
]

@router.get("/{category}/{item_id}")
async def get_item_in_category(
category: Category,
item_id: Annotated[int, Path(ge=1, title="Item ID")],
) -> dict:
item = _items.get(item_id)
if not item or item["category"] != category.value:
raise HTTPException(404, detail="Item not found in category")
return {"id": item_id, **item}
test-catalog.sh
uvicorn app.routers.catalog:router --reload --port 8003

curl "http://localhost:8003/catalog/electronics"
curl "http://localhost:8003/catalog/books/3"
curl "http://localhost:8003/catalog/electronics/99" -w "\n%{http_code}"
curl "http://localhost:8003/catalog/furniture" -w "\n%{http_code}"
# → 422 — furniture is not a valid Category enum value

What's Next