FastAPI Interactive Docs

Body - Fields

In the last post, Body - Multiple Parameters, we learned how to attach validation to whole body parameters with Body(), like Body(gt=0) on a singular value. That works great at the parameter level. But what about the fields inside a Pydantic model? šŸ¤”

Our Blog model has a title and content, but right now any string sails through. An empty title? Fine. A 5000-character title? Also fine. We want rules on the individual fields, and we want them living right inside the model where they belong. That is exactly the job of Pydantic's Field.

Importing Field

Here is the one thing that trips people up, so let's get it out of the way first.

from pydantic import BaseModel, Field

Let's review:

  1. Field comes from pydantic, not from fastapi. Notice the difference from Query, Path, and Body, which all come from fastapi.

  2. It sits next to BaseModel in the same import line, since both are Pydantic tools.

Mix this up and you will get an import error wondering where Field went. Keep it with Pydantic. šŸ™‚

Adding rules to model fields

Now let's put Field to work on our Blog model.

from typing import Annotated, Optionalfrom fastapi import Body, FastAPIfrom pydantic import BaseModel, Fieldapp = FastAPI()class Blog(BaseModel):    title: str = Field(min_length=3, max_length=100)    content: str = Field(description="The full body of the blog post")    rating: Optional[float] = Field(default=None, gt=0, le=5)    is_active: bool = True@app.put("/blog/{blog_id}")async def update_blog(blog_id: int, blog: Annotated[Blog, Body(embed=True)]):    return {"blog_id": blog_id, "blog": blog}

Let's review:

  1. title now must be between 3 and 100 characters. Send a one-letter title and FastAPI rejects it before your code runs.

  2. content carries a description. That does not validate anything, it just enriches the auto-generated docs so the field explains itself.

  3. rating is optional with a default of None, but if it is sent it must be greater than 0 and no more than 5. That is the same gt and le we used with Path numbers earlier.

  4. is_active stays a plain field with a default. Not every field needs a Field. Use it only where you want extra rules or metadata.

If a client sends a rating of 9, FastAPI sends back a clean validation error pointing right at the rating field. No manual checks anywhere in your function.

Field looks just like Query, Path, and Body

Here is the nice part. Field takes the same arguments you already know: gt, ge, lt, le, min_length, max_length, title, description, and so on. If you learned them for Query and Path, you already know Field.

class Blog(BaseModel):    title: str = Field(min_length=3, max_length=100, title="Blog title")    content: str = Field(min_length=10)    rating: Optional[float] = Field(default=None, ge=0, le=5)

Let's review:

  1. Same keyword arguments, just declared inside the model instead of in the function signature.

  2. The structure mirrors a function parameter: a type, a default, and a Field for the extras. Swap Path or Query for Field and it reads the same way.

One thing to watch with Annotated: for a field default, you can keep it simple and pass default=None straight to Field, as we did above. It keeps the optional field tidy in one place.

Field is imported from pydantic, while Query, Path, and Body come from fastapi. They behave almost identically, but they live in different packages. When an import error shows up here, this swap is usually the reason. 😁

ā­ļø Up next: Body - Nested Models. So far our Blog has been flat, just strings, numbers, and a bool. But what if a blog needs a list of tags, or an Author model nested right inside it? Pydantic handles models inside models with the same validation magic, and that is where we head next.

Practice Lab: