tiangolo/fastapi · error · ValueError

Cannot set both 'data' and 'raw_data' on the same ServerSent

Error message

Cannot set both 'data' and 'raw_data' on the same ServerSentEvent. Use 'data' for JSON-serialized payloads or 'raw_data' for pre-formatted strings.

What it means

`ServerSentEvent` has a pydantic `model_validator(mode="after")`, `_check_data_exclusive` (sse.py:148-156), that raises `ValueError` if both `data` and `raw_data` are set (both not None). `data` is JSON-serialized; `raw_data` is sent verbatim. They are mutually exclusive because the encoder must pick one serialization strategy.

Source

Thrown at fastapi/sse.py:151

        ),
    ] = None
    comment: Annotated[
        str | None,
        Doc(
            """
            Optional comment line(s).

            Comment lines start with `:` in the SSE wire format and are ignored by
            `EventSource` clients. Useful for keep-alive pings to prevent
            proxy/load-balancer timeouts.
            """
        ),
    ] = None

    @model_validator(mode="after")
    def _check_data_exclusive(self) -> "ServerSentEvent":
        if self.data is not None and self.raw_data is not None:
            raise ValueError(
                "Cannot set both 'data' and 'raw_data' on the same "
                "ServerSentEvent. Use 'data' for JSON-serialized payloads "
                "or 'raw_data' for pre-formatted strings."
            )
        return self


def _split_sse_lines(value: str) -> list[str]:
    # Split on SSE-spec line terminators only (\n, \r\n, \r), preserving
    # trailing empty strings.
    return value.replace("\r\n", "\n").replace("\r", "\n").split("\n")


def format_sse_event(
    *,
    data_str: Annotated[
        str | None,
        Doc(

View on GitHub (pinned to 42a41db11f)

Solutions

  1. Set exactly one of `data` or `raw_data` per event.
  2. When building events programmatically, pop the unused key: `evt = {"raw_data": text}; evt.pop("data", None)`.
  3. Use `data` for JSON payloads (dicts, models, even strings get quoted) and `raw_data` only for pre-formatted non-JSON text.

Example fix

# before
yield ServerSentEvent(data={"x": 1}, raw_data="custom")

# after
yield ServerSentEvent(raw_data="custom")
# or, for JSON:
yield ServerSentEvent(data={"x": 1})
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import Any
from fastapi.sse import ServerSentEvent

def make_event(*, data: Any = None, raw_data: str | None = None, **kw):
    if data is not None and raw_data is not None:
        raise ValueError("set only one of data or raw_data")
    return ServerSentEvent(data=data, raw_data=raw_data, **kw)

Type guard

def is_exclusive_payload(data, raw_data) -> bool:
    return (data is None) or (raw_data is None)

Try / catch

from fastapi.sse import ServerSentEvent

try:
    yield ServerSentEvent(data=d, raw_data=rd, event="e")
except ValueError as exc:
    # pick a deterministic fallback strategy
    payload = rd if rd is not None else d
    if isinstance(payload, str):
        yield ServerSentEvent(raw_data=payload, event="e")
    else:
        yield ServerSentEvent(data=payload, event="e")

Prevention

When it happens

Trigger: Constructing `ServerSentEvent(data={"x": 1}, raw_data="custom")`, or `ServerSentEvent(data="txt", raw_data="other")`. Often happens when forwarding an existing dict into `data` while a default `raw_data` is also supplied, or vice versa.

Common situations: Branching code that sometimes sets `data` and sometimes `raw_data` but doesn't clear the other; spreading defaults via `{**base, "data": ...}` while `raw_data` is already in `base`; refactoring from one field to the other and leaving both populated.

Related errors


AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04). Data as JSON: /data/errors/0aa9b7ff7955b8f0.json. Report an issue: GitHub.