Header parameters

You can define Headers parameters the same way you define Query, Path and Cookies parameters.

Note

Parsed Header parameter must be named parsed_headers.

Since most headers use - to separate words, but a variable like cache-control is not a valid variable name in Python. So, you would have to use aliases for field names. Remember, that headers are also case insensitive:

Run result

$ curl http://127.0.0.1:8000/api/users/ -X GET -H 'Cache-Control: max-age=0'
{"cache-control":"max-age=0","X-Client-Id":-1}

$ curl http://127.0.0.1:8000/api/users/ -D - -X GET -H 'Cache-Control: max-age=0' -H 'X-Client-Id: wrong'
HTTP/1.1 400 Bad Request
date: Thu, 09 Apr 2026 14:41:16 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 105
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"Expected `int`, got `str` - at `$.parsed_headers.X-Client-Id`","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"
      },
      "_HeadersModel": {
        "properties": {
          "X-Client-Id": {
            "default": -1,
            "type": "integer"
          },
          "cache-control": {
            "type": "string"
          }
        },
        "required": [
          "cache-control"
        ],
        "title": "_HeadersModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getApicontrollerApiApicontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "header",
            "name": "cache-control",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "deprecated": false,
            "in": "header",
            "name": "X-Client-Id",
            "schema": {
              "default": -1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_HeadersModel"
                }
              }
            },
            "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 Headers model using msgspec.Struct or pydantic.BaseModel. Other types are also supported: typing.TypedDict, dataclasses.dataclass(), etc

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

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

Duplicated headers

By default Django joins several headers into a single value. This is a limitation of a WSGI protocol. But, even ASGI workers are forced to do the same in Django for compatibility.

X-Tag: a
X-Tag: b

Would become:

{'X-Tag': 'a,b'}

To force X-Tag to be a list you can use __dmr_split_commas__. Specify lower-case header field aliases which needs to be split by a ',' char:

Run result

$ curl http://127.0.0.1:8000/api/users/ -X GET -H 'X-Tag: 1' -H 'X-Tag: 2'
{"X-Tag":[1,2]}

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"
      },
      "_HeadersModel": {
        "properties": {
          "X-Tag": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          }
        },
        "required": [
          "X-Tag"
        ],
        "title": "_HeadersModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getUsercontrollerApiUsercontroller",
        "parameters": [
          {
            "deprecated": false,
            "in": "header",
            "name": "X-Tag",
            "required": true,
            "schema": {
              "items": {
                "type": "integer"
              },
              "type": "array"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_HeadersModel"
                }
              }
            },
            "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"
          }
        }
      }
    }
  }
}

We don’t infer __dmr_split_commas__ value in any way, it is up to users to set.

Danger

Some headers like

Accept: text/html, application/json
Cache-Control: no-cache, no-store

can naturally contain values with the ',' char. If you split them, you might get a messed up value.

Customizing OpenAPI metadata for Headers

See Customizing parameter.

API Reference

dmr.components.Headers

Annotated alias for parsing header parameters.

alias of Annotated[_HeadersT, <dmr.components.HeadersComponent object at 0x79bd5a37dd00>]

class dmr.components.HeadersComponent[source]

Bases: ComponentParser

Parses request headers.

For example:

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

>>> class AuthHeaders(pydantic.BaseModel):
...     token: str = pydantic.Field(alias='X-API-Token')

>>> class UserCreateController(Controller[PydanticSerializer]):
...     def get(self, parsed_headers: Headers[AuthHeaders]) -> str:
...         return parsed_headers.token

Will parse request headers like Token: secret into AuthHeaders model.

Parameter for Headers component must be named parsed_headers.

context_name: ClassVar[str] = 'parsed_headers'

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.