If you are looking to exclude a field from JSON schema, use SkipJsonSchema:

from pydantic.json_schema import SkipJsonSchema
from pydantic import BaseModel

class MyModel(BaseModel):
    visible_in_sch: str 
    not_visible_in_sch: SkipJsonSchema[str]

You can find out more in docs.

Answer from alpintrekker on Stack Overflow
🌐
Pydantic
docs.pydantic.dev › latest › concepts › json_schema
JSON Schema - Pydantic Validation
The SkipJsonSchema annotation can be used to skip an included field (or part of a field's specifications) from the generated JSON schema. See the API docs for more details. Custom types (used as field_name: TheType or field_name: Annotated[TheType, ...
🌐
GitHub
github.com › pydantic › pydantic › issues › 8150
SkipJsonSchema does not do anything · Issue #8150 · pydantic/pydantic
November 16, 2023 - from pydantic import BaseModel from pydantic.json_schema import SkipJsonSchema from typing import Union class ModelA(BaseModel): a: Union[int, SkipJsonSchema[None]] class ModelB(BaseModel): a: int print(ModelA.model_json_schema()) print(ModelB.model_json_schema()) # output looks the same for both: # {'properties': {'a': {'title': 'A', 'type': 'integer'}}, 'required': ['a'], 'title': 'ModelA', 'type': 'object'} # {'properties': {'a': {'title': 'A', 'type': 'integer'}}, 'required': ['a'], 'title': 'ModelB', 'type': 'object'}
Author   antazoey
🌐
Instructor
python.useinstructor.com › concepts › fields
Customizing Pydantic Models with Field Metadata - Instructor
You can do this by using Pydantic's SkipJsonSchema annotation. This omits a field from the JSON schema emitted by Pydantic (which instructor uses for constructing its prompts and tool definitions).
🌐
Medium
skaaptjop.medium.com › how-i-use-pydantic-unrequired-fields-so-that-the-schema-works-0010d8758072
How I use Pydantic unrequired fields without defaults | by Will van der Leij | Medium
May 22, 2024 - from pydantic import BaseModel, Field from pydantic.json_schema import SkipJsonSchema def pop_default_from_schema(s): s.pop('default', None) class Address(BaseModel): street: str city: str class Person(BaseModel): address: Address | SkipJsonSchema[None] = Field(default=None, json_schema_extra=pop_default_from_schema) The json_schema_extra allows us to supply a callable which simply pops any ‘default’ reference in the schema dict for that field.
🌐
GitHub
github.com › pydantic › pydantic › pull › 6653
✨ Add `SkipJsonSchema` annotation by Kludex · Pull Request #6653 · pydantic/pydantic
from typing import Annotated from pydantic import BaseModel, Field from pydantic.json_schema import SkipJsonSchema class Model(BaseModel): x: Annotated[int | SkipJsonSchema[None], Field(json_schema_extra=lambda x: x.pop('default'))] = None print(Model.model_json_schema()) #> {'properties': {'x': {'title': 'X', 'type': 'integer'}}, 'title': 'Model', 'type': 'object'}
Author   pydantic
Top answer
1 of 1
1

You can use SkipJsonSchema to hide particular type (not parameter) in schema.

import uvicorn
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from pydantic.json_schema import SkipJsonSchema

app = FastAPI()

class QueryParams(BaseModel):
    param1: str
    param2: str | SkipJsonSchema[None] = None

@app.get("/")
def read_items(params: QueryParams = Depends()):
    return params

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

The code above will produce schema for param2 (notice the type is just string):

          {
            "name": "param2",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "title": "Param2"
            }
          }

And if you want to hide the parameter, you should use Query(include_in_schema=False).
The problem is that id doesn't work with Query parameters defined using BaseModel.
So, you can only use it directly:

from typing import Annotated
from fastapi import FastAPI,Query

app = FastAPI()

@app.get("/")
def read_items(
    param1: str,
    param2: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    return {"param1": param1, "param2": param2}

Or as parameters of function:

from typing import Annotated
from fastapi import Depends, FastAPI,Query
from pydantic import BaseModel

app = FastAPI()


class QueryParams(BaseModel):
    param1: str
    param2: str | None = None


async def get_params(
    param1: str,
    param2: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    return QueryParams(param1=param1, param2=param2)


@app.get("/")
def read_items(params: QueryParams = Depends(get_params)):
    return params
🌐
GitHub
github.com › pydantic › pydantic › issues › 11757
`SkipJsonSchema` on a recursive model breaks `model_json_schema` on unrelated models · Issue #11757 · pydantic/pydantic
from typing import Annotated from pydantic import BaseModel, Field from pydantic.json_schema import SkipJsonSchema class GraphNode(BaseModel): children: list["GraphNode"] class WithGraph(BaseModel): graph: GraphNode print("before skip", WithGraph.model_json_schema()) class WithoutGraph(BaseModel): graph_node: Annotated[GraphNode, Field(exclude=True), SkipJsonSchema()] print("after skip", WithGraph.model_json_schema()) print(GraphNode.model_json_schema())
Find elsewhere
🌐
GitHub
github.com › pydantic › pydantic › issues › 11601
Inconsistent behavior around None, SkipJsonSchema[None] and default value None · Issue #11601 · pydantic/pydantic
from pydantic import BaseModel, Field class MyModel(BaseModel): my_field: str | None = Field(default=None) from pydantic.json_schema import SkipJsonSchema class SkipJsonModel(BaseModel): my_field: str | SkipJsonSchema[None] = Field(default=None) # Same as above, but using SkipJsonSchema ·
🌐
PyPI
pypi.org › project › fh-pydantic-form
fh-pydantic-form · PyPI
By default, fields marked with SkipJsonSchema are hidden from forms, but you can selectively show specific ones using the keep_skip_json_fields parameter. from pydantic.json_schema import SkipJsonSchema class DocumentModel(BaseModel): title: str content: str # Hidden by default - system fields document_id: SkipJsonSchema[str] = Field( default_factory=lambda: f"doc_{uuid4().hex[:12]}", description="Internal document ID" ) created_at: SkipJsonSchema[datetime.datetime] = Field( default_factory=datetime.datetime.now, description="Creation timestamp" ) version: SkipJsonSchema[int] = Field( default=
      » pip install fh-pydantic-form
    
Published   Jan 21, 2026
Version   0.3.18
🌐
GitHub
github.com › pydantic › pydantic › issues › 11500
SkipJsonSchema annotation affects model's fields validation · Issue #11500 · pydantic/pydantic
February 27, 2025 - When using SkipJsonSchema annotation on None part of a field annotation, an additional validation error none_required is added to the field's error message ... from pydantic import BaseModel from pydantic.json_schema import SkipJsonSchema class A1(BaseModel): a: str | None = None class A2(BaseModel): a: str | SkipJsonSchema[None] = None A1(a=1) #pydantic_core._pydantic_core.ValidationError: 1 validation error for A1 #a # Input should be a valid string [type=string_type, input_value=1, input_type=int] # For further information visit https://errors.pydantic.dev/2.10/v/string_type A2(a=1) #a.str
Author   zoola969
🌐
Instructor
python.useinstructor.com › concepts › models
Using Pydantic Models for Structured Outputs - Instructor
Fields can also be omitted from the schema sent to the language model using Pydantic's SkipJsonSchema annotation.
Top answer
1 of 1
5

Option 1

You could hide/exclude a field during object instantiation/creation, by using Private model attributes. Pydantic will exclude model attributes that have a leading underscore. As described in the linked documentation:

Attributes whose name has a leading underscore are not treated as fields by Pydantic, and are not included in the model schema. Instead, these are converted into a "private attribute" which is not validated or even set during calls to __init__, model_validate, etc.

Note, though, that:

As of Pydantic v2.1.0, you will receive a NameError if trying to use the Field function with a private attribute. Because private attributes are not treated as fields (as mentioned earlier), the Field() function cannot be applied.

Thus, in Pydantic V2, you could use the PrivateAttr instead of Field function, along with the default_factory parameter, in order to define a callable that will be called to generate a dynamic default value (i.e., different for each model instance)—in this case, a UUID.

Woriking Example

from fastapi import FastAPI
from pydantic import BaseModel, PrivateAttr
from uuid import UUID, uuid4


class Foo(BaseModel):
    _id: UUID = PrivateAttr(default_factory=uuid4)
    name: str


app = FastAPI()


@app.post("/foo")
def create_foo(foo: Foo):
    print(foo._id)
    return foo

Option 2

Simply a variation of the above (see the documentation for more details), without using the PrivateAttr and default_factory. Instead, the __init__ method is directly used to automatically generate a new UUID.

Woriking Example

from fastapi import FastAPI
from pydantic import BaseModel
from uuid import UUID, uuid4


class Foo(BaseModel):
    _id: UUID
    name: str
    
    def __init__(self, **data):
        super().__init__(**data)
        self._id = uuid4()


app = FastAPI()


@app.post("/foo")
def create_foo(foo: Foo):
    print(foo._id)
    return foo

Option 3

Another way would be to use two different Pydantic models, one intended for use by the user, while the second—which should inherit from the first (base) one—is used by the backend. Similar examples are given in FastAPI's Extra Models documentation, as well as in Full Stack FastAPI Template.

Woriking Example

from fastapi import FastAPI
from pydantic import BaseModel, Field
from uuid import UUID, uuid4


class BaseFoo(BaseModel):
    name: str


class Foo(BaseFoo):
    id: UUID = Field(default_factory=uuid4)
    

app = FastAPI()


@app.post("/foo")
def create_foo(base: BaseFoo):
    foo = Foo(**base.model_dump())  # Foo(name=base.name) should work as well
    print(foo.id)
    return base

Option 4

This is a variation of Options 1 and 2, modified in a way that would allow one to define their private attribute without using an underscore (if that's a requirement in your project), but still get the same result as if any of the previous options were used.

In this case, the Field function is used. Since the attribute is meant to be hidden from the client, you should need to set the exclude attribute to True, so that if you return the Pydantic model instance back to the client, that attribute won't be included. Also, in Pydantic V2, you could use the SkipJsonSchema annotation, in order to skip that field from the generated JSON schema, as in Swagger UI autodocs, for instance (for Pydantic V1 solutions, please have a look at this github post and its related discussion).

Now, there is nothing from stopping the client to pass the hidden attribute in their JSON request body, regardless of hiding it from the schema and defining it as optional/non-required (i.e., Field(default=None)). Since this solution uses a regular Field and not PrivateAttr, using the default_factory attribute, as in Option 1 and as shown below:

class Foo(BaseModel):
    id: SkipJsonSchema[UUID] = Field(default_factory=uuid4, exclude=True)
    name: str

would not be the most suitalbe approach, as if the client passed a value for id, that value would be assigned to that field.

However, using a similar approach to Option 2, i.e., replacing default_factory with __init__ (which is used to generate the UUID for id field), even if the client passed a value for id, it would be "ignored", and the one generated by the model would be assigned to the field.

Woriking Example

from fastapi import FastAPI
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
from pydantic.json_schema import SkipJsonSchema


class Foo(BaseModel):
    id: SkipJsonSchema[UUID] = Field(default=None, exclude=True)
    name: str
    
    def __init__(self, **data):
        super().__init__(**data)
        self.id = uuid4()
    

app = FastAPI()


@app.post("/foo")
def create_foo(foo: Foo):
    print(foo.id)
    return foo

This answer and this answer might also prove helpful to future readers.

How to populate a Pydantic model without default_factory or __init__ overwriting the provided field value

While this is not an issue when using Option 3 provided above (and one could opt going for that option, if they wish), it might be when using one of the remaining options, depending on the method used to populate the model.

For instance, if you populate the model using Foo(**data), where data is an already existing model instance from your database, using one of the options described earlier (besides Option 3 that does not suffer from this issue), the _id or id value (included in the data dictionary) that is passed to the model would be replaced/overwritten by a newly generated one.

To overcome this issue, the following solutions are suggested.

Solution 1

After calling Foo(**data), you could simply replace the newly generated id with the existing one, by setting the value for that specific field to data["_id"] or data["id"] (depending on the Option used).

Example:

data = {"name": "foo", "_id": "7c4308a2-0f32-1243-b2ad-bf214a24a5aa"}
f = Foo(**data)
f._id = data["_id"]

Solution 2

Instead of using Foo(**data), which uses the model's __init__ method, and hence, a new id value is generated when called—for the sake of completeness, it should also be noted that there is an additional method, i.e., model_validate(), which is very similar to the __init__ method of the model, except it takes a dict or an object rather than keyword arguments—one could use the model_construct() method (in Pydantic V1, this used to be construct()).

As per the documentation:

Creating models without validation

Pydantic also provides the model_construct() method, which allows models to be created without validation. This can be useful in at least a few cases:

  • when working with complex data that is already known to be valid (for performance reasons)
  • when one or more of the validator functions are non-idempotent, or
  • when one or more of the validator functions have side effects that you don't want to be triggered.
Warning

model_construct() does not do any validation, meaning it can create models which are invalid. You should only ever use the model_construct() method with data which has already been validated, or that you definitely trust.

[...]

When constructing an instance using model_construct(), no __init__ method from the model or any of its parent classes will be called, even when a custom __init__ method is defined.

Example:

data = {"name": "foo", "_id": "7c4308a2-0f32-1243-b2ad-bf214a24a5aa"}
f = Foo.model_construct(**data)
🌐
Netlify
field-idempotency--pydantic-docs.netlify.app › usage › schema
Schema - pydantic
from enum import Enum from pydantic import BaseModel, Field class FooBar(BaseModel): count: int size: float = None class Gender(str, Enum): male = 'male' female = 'female' other = 'other' not_given = 'not_given' class MainModel(BaseModel): """ This is the description of the main model """ foo_bar: FooBar = Field(...) gender: Gender = Field(None, alias='Gender') snap: int = Field( 42, title='The Snap', description='this is the value of snap', gt=30, lt=50, ) class Config: title = 'Main' # this is equivilant of json.dumps(MainModel.schema(), indent=2): print(MainModel.schema_json(indent=2))