Search Topics

Search across all FastAPI topics

GitHub

Path Parameters

Core Concept

Path 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.

terminal
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.

main.py
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 int

Multiple 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.

main.py
@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.

main.py
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 Error

Try 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.

main.py
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.

main.py
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 options

The 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.

Broken code
main.py
# ❌ 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.

main.py
@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 404

Advanced 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}