Request body

Body can be anything: json, xml, application/x-www-form-urlencoded, or multipart/form-data.

It depends on the Parser that is being used for the endpoint.

Note

Parsed Body parameter must be named parsed_body.

Parsing JSON

Here’s how you can parse Body with a model:

We support msgspec.Struct via MsgspecSerializer.

Run result

$ curl http://127.0.0.1:8000/api/users/ -X PUT -d '{"username": "sobolevn", "age": 27}' -H 'Content-Type: application/json'
{"username":"sobolevn","age":27}

$ curl http://127.0.0.1:8000/api/users/ -D - -X PUT -d '{"username": "sobolevn"}' -H 'Content-Type: application/json'
HTTP/1.1 400 Bad Request
date: Sun, 26 Apr 2026 21:11:04 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 100
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"Object missing required field `age` - at `$.parsed_body`","type":"value_error"}]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "age": {
            "type": "integer"
          },
          "username": {
            "type": "string"
          }
        },
        "required": [
          "username",
          "age"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "put": {
        "deprecated": false,
        "operationId": "putUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

What happens in this example?

  1. We define a Body model using msgspec.Struct, pydantic.BaseModel, attrs.define(), typing.TypedDict, or dataclasses.dataclass(). Basically, model definition is only limited by the BaseSerializer support

  2. Next, we use Body component, provide the model as a type parameter, and subclass it when defining Controller type

  3. Then we use self.parsed_body that will have the correct model type

Parsing MsgPack

Note

This feature requires 'django-modern-rest[msgpack]' to be installed.

MsgPack is a binary, compact and really fast format for modern APIs. Docs: https://msgpack.org

Bodies can be parsed using different dmr.parsers.Parser types. See our Content negotiation guide on more information about content negotiations.

Here’s how msgpack will represent {"username": "example", "age": 22} (since it is a binary format, it will show some random unicode symbols:

The only visible difference from parsing JSON is specifying a different parsers instance.

Run result

$ curl http://127.0.0.1:8000/api/users/ -X PUT --data-binary @examples/components/body.msgpack -H 'Content-Type: application/msgpack'
{"username":"example","age":22}

$ curl http://127.0.0.1:8000/api/users/ -D - -X PUT --data-binary @examples/components/body_wrong.msgpack -H 'Content-Type: application/msgpack'
HTTP/1.1 400 Bad Request
date: Sun, 26 Apr 2026 21:11:08 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 100
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"Object missing required field `age` - at `$.parsed_body`","type":"value_error"}]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "age": {
            "type": "integer"
          },
          "username": {
            "type": "string"
          }
        },
        "required": [
          "username",
          "age"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "put": {
        "deprecated": false,
        "operationId": "putUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "application/msgpack": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

Customizing OpenAPI metadata for Body

See Customizing media types.

Parsing forms

Note

We don’t recommend using forms. If you can avoid using this feature and switch to json – you totally should.

Forms are only needed for compatibility with older APIs, strange libs, existing workflows.

Here’s an example how one can send application/x-www-form-urlencoded form data to an API endpoint with the help of FormUrlEncodedParser:

Run result

$ curl http://127.0.0.1:8000/api/users/ -X PUT -d 'username=sobolevn&age=27' -H 'Content-Type: application/x-www-form-urlencoded'
{"username":"sobolevn","age":27}

$ curl http://127.0.0.1:8000/api/users/ -D - -X PUT -d 'username=sobolevn&age=wrong' -H 'Content-Type: application/x-www-form-urlencoded'
HTTP/1.1 400 Bad Request
date: Sun, 26 Apr 2026 21:11:09 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 141
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"Input should be a valid integer, unable to parse string as an integer","loc":["parsed_body","age"],"type":"value_error"}]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "age": {
            "title": "Age",
            "type": "integer"
          },
          "username": {
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "age"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "put": {
        "deprecated": false,
        "operationId": "putUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

Forcing lists and casting nulls in forms

Warning

All of the features below only work for application/x-www-form-urlencoded and multipart/form-data parsers. Json and other “modern” formats are not affected.

Django’s form parsing algorithm is 20+ years old at the moment of writing this doc.

There are some known quirks to it.

Forcing lists

Django uses django.utils.datastructures.MultiValueDict to store body data, when parsing forms. Due to its API, it does not give list objects back easily. So, when we need a list for a field, we need to force it like this:

Run result

$ curl http://127.0.0.1:8000/api/users/ -X PUT -F username=sobolevn -F tags=python -F tags=django -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","tags":["python","django"]}

$ curl http://127.0.0.1:8000/api/users/ -X PUT -F username=sobolevn -F tags=single -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","tags":["single"]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "tags": {
            "items": {
              "type": "string"
            },
            "title": "Tags",
            "type": "array"
          },
          "username": {
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "tags"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "put": {
        "deprecated": false,
        "operationId": "putUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

Split commas

Another problem that might happen is that some field might look like {'foo': 'bar,baz'}, not {'foo': ['bar', 'baz']}. To solve this, one can use a different magic attribute:

Run result

$ curl http://127.0.0.1:8000/api/users/ -X POST -F username=sobolevn -F tags=python,django -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","tags":["python","django"]}

$ curl http://127.0.0.1:8000/api/users/ -X POST -F username=sobolevn -F tags=single -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","tags":["single"]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "tags": {
            "items": {
              "type": "string"
            },
            "title": "Tags",
            "type": "array"
          },
          "username": {
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "tags"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "Created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

Warning

We split all data by ',', if your data contains ',' as a regular value, it might be corrupted.

Be careful to use this with fields which do not contain ','. Like list of ints, uuids, or slugs.

Casting nulls

It is hard to pass None as a value in a form. To solve the need for None many places offer to pass 'null' as a string. We can cast 'null' back to None if __dmr_cast_null__ is specified.

Run result

$ curl http://127.0.0.1:8000/api/users/ -X POST -F username=sobolevn -F promo_id=1 -F tags=python -F tags=django -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","promo_id":1,"tags":["python","django"]}

$ curl http://127.0.0.1:8000/api/users/ -X POST -F username=sobolevn -F promo_id=null -F tags=python -F tags=null -H 'Content-Type: multipart/form-data'
{"username":"sobolevn","promo_id":null,"tags":["python",null]}

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      },
      "_User": {
        "properties": {
          "promo_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Promo Id"
          },
          "tags": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "title": "Tags",
            "type": "array"
          },
          "username": {
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "promo_id",
          "tags"
        ],
        "title": "_User",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/_User"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_User"
                }
              }
            },
            "description": "Created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

You can combine this feature with both __dmr_split_commas__ and __dmr_force_list__ as well.

API Reference

dmr.components.Body

Annotated alias for parsing requests bodies.

alias of Annotated[_BodyT, <dmr.components.BodyComponent object at 0x7a3ebe2f3700>]

class dmr.components.BodyComponent[source]

Bases: ComponentParser

Parses body of the request.

For example:

>>> import pydantic
>>> from dmr import Body, Controller
>>> from dmr.plugins.pydantic import PydanticSerializer

>>> class UserCreateInput(pydantic.BaseModel):
...     email: str
...     age: int

>>> class UserCreateController(Controller[PydanticSerializer]):
...     def post(self, parsed_body: Body[UserCreateInput]) -> str:
...         return parsed_body.email

Will parse a body like {'email': 'user@example.org', 'age': 18} into UserCreateInput model.

Parameter for Body component must be named parsed_body.

When working with parsers that support dmr.parsers.SupportsDjangoDefaultParsing interface, you can specify __dmr_split_commas__ attribute: it must contain a frozenset of field aliases that will be split by ',' char.

conditional_types(model: Any, model_meta: tuple[Any, ...]) Mapping[str, Any][source]

Provide conditional parsing types based on content type.

Body model can be conditional based on a content_type. If typing.Annotated is passed together with dmr.negotiation.conditional_type() we treat the body as conditional. Otherwise, returns an empty dict.

context_name: ClassVar[str] = 'parsed_body'

All subtypes must provide a unique name that will be used to parse context.

We use a single context for all parsing, this component will live under a dict field with this name.

get_schema(model: Any, model_meta: tuple[Any, ...], metadata: EndpointMetadata, serializer: type[BaseSerializer], context: OpenAPIContext) list[Parameter | Reference] | RequestBody[source]

Generate OpenAPI spec for component.

provide_context_data(endpoint: Endpoint, controller: Controller[BaseSerializer], *, field_model: Any) Any[source]

Return unstructured raw values for serializer.from_python().

It must return the same number of elements that has type vars. Basically, each type var is a model. Each element in a tuple is the corresponding data for that model.

When this method returns not a tuple and there’s only one type variable, it also works.