tiangolo/fastapi · error · ValueError
Cannot set both 'data' and 'raw_data' on the same…
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
Raised by `ServerSentEvent._check_data_exclusive` (fastapi/sse.py:151, a `model_validator(mode='after')`) as a ValueError when both `data` and `raw_data` are set on the same `ServerSentEvent`. They are mutually exclusive: `data` is always JSON-serialized, `raw_data` is sent verbatim as the `data:` field. Setting both is ambiguous, so FastAPI rejects it at model validation time.
Solutions
- Use only `data` when you want JSON serialization: `ServerSentEvent(data=payload)`.
- Use only `raw_data` when you want the string verbatim: `ServerSentEvent(raw_data=text)`.
- In generic builders, branch on input type and set exactly one of the two fields.
Example fix
// before
yield ServerSentEvent(data={'msg': 'hi'}, raw_data='hi')
// after
yield ServerSentEvent(data={'msg': 'hi'}) Defensive patterns
Strategy: type-guard
Validate before calling
from fastapi.sse import ServerSentEvent
def build_event(*, data=None, raw_data=None, **kw) -> ServerSentEvent:
if data is not None and raw_data is not None:
raise ValueError('pass exactly one of data or raw_data')
return ServerSentEvent(data=data, raw_data=raw_data, **kw) Type guard
def exactly_one_payload(data, raw_data) -> bool:
return (data is not None) ^ (raw_data is not None) Prevention
- Branch in generic builders: choose data XOR raw_data based on input type.
- Use mypy to flag code paths that could set both fields.
- Prefer data for structured payloads; reserve raw_data for pre-formatted text.
When it happens
Trigger: Constructing `ServerSentEvent(data={'k': 1}, raw_data='hello')`. Passing both keys in a dict: `ServerSentEvent(**{'data': x, 'raw_data': y})`. Building events generically and accidentally populating both fields.
Common situations: Generic event builders that accept both options. Migrating from `raw_data` to `data` (or vice versa) and forgetting to clear the old field. Copy-paste from an example that set one, then overriding the other.
Related errors
- SSE ' ' must be a single line
- SSE 'id' must not contain null characters
- Code block (lines - ) has different language than the…
- {"errors": exc.errors(), "body": body.decode()}
- {"errors": exc.errors(), "body": body.decode()}
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/0aa9b7ff7955b8f0.
Report an issue: GitHub.
Appendix: 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 3e8d1526d8)