#3447·starlette

Add configurable media handlers and Request.data()

Author: KludexCreated Aug 21, 2026Updated Aug 22, 2026
Labelsfeature

Summary

Add application-level media handlers and a generic Request.data() method. This would let third-party packages add support for formats such as MessagePack, CBOR, XML, and YAML without replacing Request or modifying Starlette.

python
from starlette.applications import Starlette
from starlette_msgpack import MessagePackHandler

app = Starlette(
    media_handlers={
        "application/msgpack": MessagePackHandler(),
        "application/x-msgpack": MessagePackHandler(),
    }
)


async def endpoint(request):
    data = await request.data()
    ...

Request.body() and Request.stream() would keep returning raw bytes. A handler could choose either API depending on whether it needs to buffer or incrementally consume the request.

Handler protocol

The smallest useful public interface is a callable that receives the request:

python
from __future__ import annotations

from typing import Any, Protocol

from starlette.requests import Request


class MediaHandler(Protocol):
    async def __call__(self, request: Request) -> Any: ...

For example, a buffered MessagePack handler could use Request.body():

python
from __future__ import annotations

from typing import Any

import msgpack

from starlette.requests import Request


class MessagePackHandler:
    async def __call__(self, request: Request) -> Any:
        return msgpack.unpackb(await request.body(), raw=False)

A multipart or sequence handler could consume Request.stream() instead. The protocol does not require Starlette to buffer the body or expose the parser implementation.

Proposed behavior

  • Add media_handlers: Mapping[str, MediaHandler] | None = None to Starlette.
  • Add await request.data() to select a handler from the normalized request Content-Type and cache its result.
  • Keep Request.body(), Request.stream(), Request.json(), and Request.form() backward-compatible.
  • Provide built-in handlers for JSON, URL-encoded forms, and multipart forms.
  • Let application handlers override a built-in media type.
  • Raise an unsupported-media-type error when no handler matches.
  • Keep decoded streaming APIs out of the initial proposal. Specialized packages can consume Request.stream() directly.

Exact media types should take precedence. Matching structured suffixes such as application/*+json can be considered as part of the implementation.

Motivation

Starlette currently exposes format-specific methods for JSON and forms. Supporting another media type requires each endpoint to select and invoke a decoder itself, or requires a custom request class. An application-level registry gives dependencies one integration point while keeping the request body APIs small and preserving ASGI streaming and backpressure.

This also keeps implementation choices private. For example, a multipart handler can select between available parser backends while Starlette continues to own limits, temporary files, cleanup, and UploadFile behavior.

Previous discussions

  • Discussion #2012 collected the custom request parser proposals. It specifically considered application-level parsers selected by content type and a generic request parsing method.
  • Issue #396 included a general request.parse() method in an earlier API sketch.
  • Issue #715 requested configurable JSON decoding.
  • Issue #78 and issue #304 requested alternative JSON implementations.
  • Issue #388, issue #697, and issue #849 requested customizable or streaming multipart handling.
  • PR #875 and PR #930 explored custom request classes. The discussion favored generic parsing support on the built-in Request instead.