Skip to main content

Understanding Annotated

Throughout this course, you’ve seen two different ways of writing validations and dependencies. For example, in Pydantic models, we previously wrote:
Later, we switched to the recommended syntax:
Similarly, for Dependency Injection, we can write:
or the recommended syntax:
Both styles are equivalent. However, Annotated is the recommended approach in modern FastAPI and Pydantic.

Why Use Annotated?

The purpose of Annotated is to separate the actual data type from its metadata. General syntax:
  • Type describes the expected data type.
  • Metadata provides additional instructions such as validation rules or dependency information.

Common Examples

Notice that only the metadata changes.

How to Read Annotated

Consider this example.
It can be read as:
  • The value is a string.
  • It must satisfy the validation rules defined by Field().
Likewise,
can be read as:
  • The value is an EmployeeRepository.
  • FastAPI should obtain it by calling get_employee_repository().

Field() vs Depends()

Although both are used with Annotated, they serve different purposes.

Field()

Used inside Pydantic models to validate data.
Here:
  • Type: str
  • Metadata: Field(min_length=2)

Depends()

Used by FastAPI for Dependency Injection.
Here:
  • Type: EmployeeRepository
  • Metadata: Depends(get_employee_repository)
Unlike Field(), Depends() does not perform validation. Instead, it tells FastAPI how to create the required object.

Creating Reusable Dependency Aliases

Suppose many endpoints need an EmployeeRepository. Instead of repeatedly writing:
we can create a reusable alias.
Now the endpoint becomes much cleaner.
FastAPI expands this alias internally to:

Mental Model

Think of Annotated as attaching extra information to a type.
Examples:

Key Takeaway

Annotated always combines two things:
  • The actual type (str, int, EmployeeRepository, etc.).
  • Metadata that describes how the value should be validated or obtained.
The metadata depends on the context:
  • Field() → Request body validation.
  • Query() → Query parameter validation.
  • Path() → Path parameter validation.
  • Header() → Header extraction.
  • Depends() → Dependency Injection.
Once you understand this pattern, you’ll notice that FastAPI and Pydantic use Annotated consistently throughout the framework.

Annotated and Default Values

A common question is:
If Annotated moves the metadata inside the type annotation, where do default values go?
The answer is simple:
  • Validation metadata goes inside Annotated.
  • Default values are still assigned using =.

Example 1: Field()

Here:
  • str is the type.
  • Field(...) defines the validation rules.
  • "Engineering" is the default value.

Example 2: Query()

Here:
  • int is the type.
  • Query(...) defines the validation rules.
  • 10 is the default value.

Example 3: Header()

Here:
  • str is the type.
  • Header() tells FastAPI to read the value from the request header.
  • "en" is the default value if the header is not provided.

Example 4: Depends()

Although less common, the same syntax applies.
Notice that there is no default value here because FastAPI always injects the dependency.

General Rule

  • Type → What kind of value is expected.
  • Metadata → Validation or dependency information.
  • DefaultValue → Used only when the value is optional.

Examples

Key Takeaway

Think of the syntax as three separate parts:
  • Type defines what the value is.
  • Metadata defines how FastAPI or Pydantic should process it.
  • DefaultValue defines what to use when the value is optional.