- Additional validation
- Use
Annotatedin the type for theqparameter - Add
QuerytoAnnotatedin theqparameter - Alternative (old):
Queryas the default value - Add more validations
- Add regular expressions
- Default values
- Required parameters
- Query parameter list / multiple values
- Declare more metadata
- Alias parameters
- Deprecating parameters
- Exclude parameters from OpenAPI
- Custom Validation
- Recap
Query Parameters and String Validations¶
FastAPI allows you to declare additional information and validation for your parameters.
Let's take this application as example:
fromfastapiimportFastAPIapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=None):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsThe query parameterq is of typestr | None, that means that it's of typestr but could also beNone, and indeed, the default value isNone, so FastAPI will know it's not required.
Note
FastAPI will know that the value ofq is not required because of the default value= None.
Havingstr | None will allow your editor to give you better support and detect errors.
Additional validation¶
We are going to enforce that even thoughq is optional, whenever it is provided,its length doesn't exceed 50 characters.
ImportQuery andAnnotated¶
To achieve that, first import:
QueryfromfastapiAnnotatedfromtyping
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(max_length=50)]=None):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,max_length=50)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsInfo
FastAPI added support forAnnotated (and started recommending it) in version 0.95.0.
If you have an older version, you would get errors when trying to useAnnotated.
Make sure youUpgrade the FastAPI version to at least 0.95.1 before usingAnnotated.
UseAnnotated in the type for theq parameter¶
Remember I told you before thatAnnotated can be used to add metadata to your parameters in thePython Types Intro?
Now it's the time to use it with FastAPI. 🚀
We had this type annotation:
q:str|None=NoneWhat we will do is wrap that withAnnotated, so it becomes:
q:Annotated[str|None]=NoneBoth of those versions mean the same thing,q is a parameter that can be astr orNone, and by default, it isNone.
Now let's jump to the fun stuff. 🎉
AddQuery toAnnotated in theq parameter¶
Now that we have thisAnnotated where we can put more information (in this case some additional validation), addQuery inside ofAnnotated, and set the parametermax_length to50:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(max_length=50)]=None):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,max_length=50)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsNotice that the default value is stillNone, so the parameter is still optional.
But now, havingQuery(max_length=50) inside ofAnnotated, we are telling FastAPI that we want it to haveadditional validation for this value, we want it to have maximum 50 characters. 😎
Tip
Here we are usingQuery() because this is aquery parameter. Later we will see others likePath(),Body(),Header(), andCookie(), that also accept the same arguments asQuery().
FastAPI will now:
- Validate the data making sure that the max length is 50 characters
- Show aclear error for the client when the data is not valid
- Document the parameter in the OpenAPI schemapath operation (so it will show up in theautomatic docs UI)
Alternative (old):Query as the default value¶
Previous versions of FastAPI (before0.95.0) required you to useQuery as the default value of your parameter, instead of putting it inAnnotated, there's a high chance that you will see code using it around, so I'll explain it to you.
Tip
For new code and whenever possible, useAnnotated as explained above. There are multiple advantages (explained below) and no disadvantages. 🍰
This is how you would useQuery() as the default value of your function parameter, setting the parametermax_length to 50:
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,max_length=50)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(max_length=50)]=None):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsAs in this case (without usingAnnotated) we have to replace the default valueNone in the function withQuery(), we now need to set the default value with the parameterQuery(default=None), it serves the same purpose of defining that default value (at least for FastAPI).
So:
q:str|None=Query(default=None)...makes the parameter optional, with a default value ofNone, the same as:
q:str|None=NoneBut theQuery version declares it explicitly as being a query parameter.
Then, we can pass more parameters toQuery. In this case, themax_length parameter that applies to strings:
q:str|None=Query(default=None,max_length=50)This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schemapath operation.
Query as the default value or inAnnotated¶
Keep in mind that when usingQuery inside ofAnnotated you cannot use thedefault parameter forQuery.
Instead, use the actual default value of the function parameter. Otherwise, it would be inconsistent.
For example, this is not allowed:
q:Annotated[str,Query(default="rick")]="morty"...because it's not clear if the default value should be"rick" or"morty".
So, you would use (preferably):
q:Annotated[str,Query()]="rick"...or in older code bases you will find:
q:str=Query(default="rick")Advantages ofAnnotated¶
UsingAnnotated is recommended instead of the default value in function parameters, it isbetter for multiple reasons. 🤓
Thedefault value of thefunction parameter is theactual default value, that's more intuitive with Python in general. 😌
You couldcall that same function inother places without FastAPI, and it wouldwork as expected. If there's arequired parameter (without a default value), youreditor will let you know with an error,Python will also complain if you run it without passing the required parameter.
When you don't useAnnotated and instead use the(old) default value style, if you call that function without FastAPI inother places, you have toremember to pass the arguments to the function for it to work correctly, otherwise the values will be different from what you expect (e.g.QueryInfo or something similar instead ofstr). And your editor won't complain, and Python won't complain running that function, only when the operations inside error out.
BecauseAnnotated can have more than one metadata annotation, you could now even use the same function with other tools, likeTyper. 🚀
Add more validations¶
You can also add a parametermin_length:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(min_length=3,max_length=50)]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,min_length=3,max_length=50)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsAdd regular expressions¶
You can define aregular expressionpattern that the parameter should match:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(min_length=3,max_length=50,pattern="^fixedquery$")]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,min_length=3,max_length=50,pattern="^fixedquery$"),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsThis specific regular expression pattern checks that the received parameter value:
^: starts with the following characters, doesn't have characters before.fixedquery: has the exact valuefixedquery.$: ends there, doesn't have any more characters afterfixedquery.
If you feel lost with all these"regular expression" ideas, don't worry. They are a hard topic for many people. You can still do a lot of stuff without needing regular expressions yet.
Now you know that whenever you need them you can use them inFastAPI.
Default values¶
You can, of course, use default values other thanNone.
Let's say that you want to declare theq query parameter to have amin_length of3, and to have a default value of"fixedquery":
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str,Query(min_length=3)]="fixedquery"):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str=Query(default="fixedquery",min_length=3)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsNote
Having a default value of any type, includingNone, makes the parameter optional (not required).
Required parameters¶
When we don't need to declare more validations or metadata, we can make theq query parameter required just by not declaring a default value, like:
q:strinstead of:
q:str|None=NoneBut we are now declaring it withQuery, for example like:
q:Annotated[str|None,Query(min_length=3)]=NoneSo, when you need to declare a value as required while usingQuery, you can simply not declare a default value:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str,Query(min_length=3)]):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str=Query(min_length=3)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsRequired, can beNone¶
You can declare that a parameter can acceptNone, but that it's still required. This would force clients to send a value, even if the value isNone.
To do that, you can declare thatNone is a valid type but simply do not declare a default value:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(min_length=3)]):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(min_length=3)):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsQuery parameter list / multiple values¶
When you define a query parameter explicitly withQuery you can also declare it to receive a list of values, or said in another way, to receive multiple values.
For example, to declare a query parameterq that can appear multiple times in the URL, you can write:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[list[str]|None,Query()]=None):query_items={"q":q}returnquery_items🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:list[str]|None=Query(default=None)):query_items={"q":q}returnquery_itemsThen, with a URL like:
http://localhost:8000/items/?q=foo&q=baryou would receive the multipleqquery parameters' values (foo andbar) in a Pythonlist inside yourpath operation function, in thefunction parameterq.
So, the response to that URL would be:
{"q":["foo","bar"]}Tip
To declare a query parameter with a type oflist, like in the example above, you need to explicitly useQuery, otherwise it would be interpreted as a request body.
The interactive API docs will update accordingly, to allow multiple values:

Query parameter list / multiple values with defaults¶
You can also define a defaultlist of values if none are provided:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[list[str],Query()]=["foo","bar"]):query_items={"q":q}returnquery_items🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:list[str]=Query(default=["foo","bar"])):query_items={"q":q}returnquery_itemsIf you go to:
http://localhost:8000/items/the default ofq will be:["foo", "bar"] and your response will be:
{"q":["foo","bar"]}Using justlist¶
You can also uselist directly instead oflist[str]:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[list,Query()]=[]):query_items={"q":q}returnquery_items🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:list=Query(default=[])):query_items={"q":q}returnquery_itemsNote
Keep in mind that in this case, FastAPI won't check the contents of the list.
For example,list[int] would check (and document) that the contents of the list are integers. Butlist alone wouldn't.
Declare more metadata¶
You can add more information about the parameter.
That information will be included in the generated OpenAPI and used by the documentation user interfaces and external tools.
Note
Keep in mind that different tools might have different levels of OpenAPI support.
Some of them might not show all the extra information declared yet, although in most of the cases, the missing feature is already planned for development.
You can add atitle:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(title="Query string",min_length=3)]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,title="Query string",min_length=3),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsAnd adescription:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsAlias parameters¶
Imagine that you want the parameter to beitem-query.
Like in:
http://127.0.0.1:8000/items/?item-query=foobaritemsButitem-query is not a valid Python variable name.
The closest would beitem_query.
But you still need it to be exactlyitem-query...
Then you can declare analias, and that alias is what will be used to find the parameter value:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(alias="item-query")]=None):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,alias="item-query")):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsDeprecating parameters¶
Now let's say you don't like this parameter anymore.
You have to leave it there a while because there are clients using it, but you want the docs to clearly show it asdeprecated.
Then pass the parameterdeprecated=True toQuery:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresultsThe docs will show it like this:

Exclude parameters from OpenAPI¶
To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameterinclude_in_schema ofQuery toFalse:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(hidden_query:Annotated[str|None,Query(include_in_schema=False)]=None,):ifhidden_query:return{"hidden_query":hidden_query}else:return{"hidden_query":"Not found"}🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(hidden_query:str|None=Query(default=None,include_in_schema=False),):ifhidden_query:return{"hidden_query":hidden_query}else:return{"hidden_query":"Not found"}Custom Validation¶
There could be cases where you need to do somecustom validation that can't be done with the parameters shown above.
In those cases, you can use acustom validator function that is applied after the normal validation (e.g. after validating that the value is astr).
You can achieve that usingPydantic'sAfterValidator inside ofAnnotated.
Tip
Pydantic also hasBeforeValidator and others. 🤓
For example, this custom validator checks that the item ID starts withisbn- for anISBN book number or withimdb- for anIMDB movie URL ID:
importrandomfromtypingimportAnnotatedfromfastapiimportFastAPIfrompydanticimportAfterValidatorapp=FastAPI()data={"isbn-9781529046137":"The Hitchhiker's Guide to the Galaxy","imdb-tt0371724":"The Hitchhiker's Guide to the Galaxy","isbn-9781439512982":"Isaac Asimov: The Complete Stories, Vol. 2",}defcheck_valid_id(id:str):ifnotid.startswith(("isbn-","imdb-")):raiseValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')returnid@app.get("/items/")asyncdefread_items(id:Annotated[str|None,AfterValidator(check_valid_id)]=None,):ifid:item=data.get(id)else:id,item=random.choice(list(data.items()))return{"id":id,"name":item}Info
This is available with Pydantic version 2 or above. 😎
Tip
If you need to do any type of validation that requires communicating with anyexternal component, like a database or another API, you should instead useFastAPI Dependencies, you will learn about them later.
These custom validators are for things that can be checked withonly thesame data provided in the request.
Understand that Code¶
The important point is just usingAfterValidator with a function insideAnnotated. Feel free to skip this part. 🤸
But if you're curious about this specific code example and you're still entertained, here are some extra details.
String withvalue.startswith()¶
Did you notice? a string usingvalue.startswith() can take a tuple, and it will check each value in the tuple:
# Code above omitted 👆defcheck_valid_id(id:str):ifnotid.startswith(("isbn-","imdb-")):raiseValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')returnid# Code below omitted 👇👀 Full file preview
importrandomfromtypingimportAnnotatedfromfastapiimportFastAPIfrompydanticimportAfterValidatorapp=FastAPI()data={"isbn-9781529046137":"The Hitchhiker's Guide to the Galaxy","imdb-tt0371724":"The Hitchhiker's Guide to the Galaxy","isbn-9781439512982":"Isaac Asimov: The Complete Stories, Vol. 2",}defcheck_valid_id(id:str):ifnotid.startswith(("isbn-","imdb-")):raiseValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')returnid@app.get("/items/")asyncdefread_items(id:Annotated[str|None,AfterValidator(check_valid_id)]=None,):ifid:item=data.get(id)else:id,item=random.choice(list(data.items()))return{"id":id,"name":item}A Random Item¶
Withdata.items() we get aniterable object with tuples containing the key and value for each dictionary item.
We convert this iterable object into a properlist withlist(data.items()).
Then withrandom.choice() we can get arandom value from the list, so, we get a tuple with(id, name). It will be something like("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy").
Then weassign those two values of the tuple to the variablesid andname.
So, if the user didn't provide an item ID, they will still receive a random suggestion.
...we do all this in asingle simple line. 🤯 Don't you love Python? 🐍
# Code above omitted 👆@app.get("/items/")asyncdefread_items(id:Annotated[str|None,AfterValidator(check_valid_id)]=None,):ifid:item=data.get(id)else:id,item=random.choice(list(data.items()))return{"id":id,"name":item}👀 Full file preview
importrandomfromtypingimportAnnotatedfromfastapiimportFastAPIfrompydanticimportAfterValidatorapp=FastAPI()data={"isbn-9781529046137":"The Hitchhiker's Guide to the Galaxy","imdb-tt0371724":"The Hitchhiker's Guide to the Galaxy","isbn-9781439512982":"Isaac Asimov: The Complete Stories, Vol. 2",}defcheck_valid_id(id:str):ifnotid.startswith(("isbn-","imdb-")):raiseValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')returnid@app.get("/items/")asyncdefread_items(id:Annotated[str|None,AfterValidator(check_valid_id)]=None,):ifid:item=data.get(id)else:id,item=random.choice(list(data.items()))return{"id":id,"name":item}Recap¶
You can declare additional validations and metadata for your parameters.
Generic validations and metadata:
aliastitledescriptiondeprecated
Validations specific for strings:
min_lengthmax_lengthpattern
Custom validations usingAfterValidator.
In these examples you saw how to declare validations forstr values.
See the next chapters to learn how to declare validations for other types, like numbers.







