Schema Configuration¶
Schema is a pydantic.BaseModel, so every Pydantic
model_config option is available on it. This
page covers the handful that matter most day to day in Django Ninja — generating camelCase
output, rejecting unknown fields, and validating on assignment — plus how they interact with
from_orm/from_attributes and the operation-level by_alias argument.
Setting config¶
Set model_config on the schema itself, exactly as you would on any Pydantic model:
from pydantic import ConfigDict
from ninja import Schema
class UserSchema(Schema):
model_config = ConfigDict(extra="forbid")
id: int
email: str
Schema itself sets model_config = ConfigDict(from_attributes=True) so it can read from
Django model instances as well as dicts. Pydantic merges a subclass's model_config with
what it inherits, so setting your own model_config doesn't lose from_attributes — you only
need to repeat it if you're deliberately turning it off.
ModelSchema and a class built with create_schema() are also Schema subclasses, so
model_config works the same way on those too.
camelCase output with alias_generator¶
APIs consumed by JavaScript clients often use camelCase field names while the Python/Django side
stays snake_case. Pydantic's
alias_generator
config option handles this — set it to a function that turns a field name into its alias, and
Pydantic ships a ready-made one for camelCase:
from pydantic import ConfigDict
from pydantic.alias_generators import to_camel
from ninja import Schema
class UserSchema(Schema):
model_config = ConfigDict(alias_generator=to_camel)
id: int
is_staff: bool
Two more things are required to actually get camelCase in and out:
populate_by_name=Trueon the schema — without it, the schema only accepts the alias (isStaff) as input, which breaksfrom_orm()/from_attributessince Django objects and dicts are still keyed by the originalis_staff.by_alias=Trueon the operation (or on aRouter, which applies to every operation registered on it) — without it, serialization uses the plain field names regardless ofalias_generator. See Responses for where elseby_aliascan be set.
from pydantic import ConfigDict
from pydantic.alias_generators import to_camel
from django.contrib.auth.models import User
from ninja import NinjaAPI, ModelSchema
class UserSchema(ModelSchema):
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True, # required for from_orm() to still accept is_staff
)
class Meta:
model = User
fields = ["id", "email", "is_staff"]
api = NinjaAPI()
@api.get("/users", response=list[UserSchema], by_alias=True)
def get_users(request):
return User.objects.all()
[
{"id": 1, "email": "[email protected]", "isStaff": true},
{"id": 2, "email": "[email protected]", "isStaff": false}
]
Tip
pydantic.alias_generators also ships to_pascal and to_snake, and you can pass any
plain function — alias_generator=lambda field_name: field_name.upper() works too.
Rejecting unknown fields with extra¶
By default (Pydantic's own default, extra="ignore"), a Schema used as a request body
silently drops any field in the payload that isn't declared. Set extra="forbid" to reject
those requests instead:
from pydantic import ConfigDict
from ninja import NinjaAPI, Schema
class TaskIn(Schema):
model_config = ConfigDict(extra="forbid")
title: str
is_completed: bool = False
api = NinjaAPI()
@api.post("/tasks")
def create_task(request, payload: TaskIn):
...
Posting {"title": "Write docs", "typo_field": true} to this operation now fails with a 422
and a Pydantic extra_forbidden error for typo_field, instead of the extra key being
silently discarded. The third option, extra="allow", keeps unknown fields around and makes
them accessible as regular attributes on the validated instance.
extra is checked against incoming data, so it matters for schemas used as a request Body,
Query, etc. — it has no effect on what a response= schema outputs, since serialization
only ever emits the fields the schema declares.
Validating on assignment with validate_assignment¶
Pydantic doesn't re-run validation when you set an attribute on an already-constructed instance,
by default — validate_assignment=True turns that on:
from pydantic import ConfigDict
from ninja import Schema
class TaskIn(Schema):
model_config = ConfigDict(validate_assignment=True)
title: str
task = TaskIn(title="Write docs")
task.title = "Write docs " # revalidated and stored
task.title = 123 # raises pydantic.ValidationError
This is most useful for a schema you build up or mutate after construction — for example one
you pass through a chain of helper functions before returning it as a response=.
Other Pydantic config¶
Everything else in Pydantic's ConfigDict —
str_strip_whitespace, str_to_lower, frozen, title, json_schema_extra, and the rest —
works the same on a Schema as it does on a plain BaseModel; nothing about Django Ninja
changes their behavior.
Edge cases & tips¶
model_configmerges up the inheritance chain — a subclass only needs to set the keys it wants to change, as shown above forfrom_attributes.populate_by_nameis what makesalias_generatorcompatible withfrom_orm(). Without it, validating from a Django object or a plain dict (both still keyed by the original field names) fails with a "field required" error for every renamed field.by_aliasfor output is set on the operation orRouter— not on the schema.alias_generatoronly defines what the alias is; whether it's actually used when rendering a response is a separate, per-operation switch. See Responses.- Incoming request bodies are always documented in OpenAPI using aliases, independent of
by_alias— that argument only affects response serialization (and its OpenAPI schema). extraonly governs validation (input), not serialization (output). Usefields/excludeonModelSchema, or simply don't declare the field, to control what a response includes.