reflex-dev/reflex · error · EventHandlerTypeError
Arguments to event handlers must be Vars or JSON-serializabl
Error message
Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}. What it means
Event-handler arguments become part of the JSON event payload sent to the browser; anything that is not a Var or JSON-serializable cannot be represented and the LiteralVar.create call raises TypeError, which is wrapped in this EventHandlerTypeError.
Source
Thrown at packages/reflex-base/src/reflex_base/event/__init__.py:669
# Special case for file uploads. The upload arg takes its own
# positional slot so the remaining args stay aligned with fn_args,
# but its parameter name is re-derived server-side in as_event_spec.
if isinstance(arg, (FileUpload, UploadFilesChunk)):
if upload_event_spec is not None:
msg = (
f"Event handler {self.fn.__name__} received multiple file "
"upload arguments."
)
raise EventHandlerTypeError(msg)
upload_event_spec = arg.as_event_spec(handler=self)
continue
# Otherwise, convert to JSON.
try:
payload.append((Var(_js_expr=fn_arg), LiteralVar.create(arg)))
except TypeError as e:
msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
raise EventHandlerTypeError(msg) from e
if upload_event_spec is not None:
if not payload:
return upload_event_spec
# The extra bound args share the flat payload with the synthetic
# upload args, so reject names that would clobber a reserved upload
# key (files, upload_id, extra_headers, ...).
payload_names = [name._js_expr for name, _ in payload]
reserved = {name._js_expr for name, _ in upload_event_spec.args}
clash = next((name for name in payload_names if name in reserved), None)
if clash is not None:
msg = (
f"Event handler {self.fn.__name__} argument {clash!r} conflicts "
"with a reserved upload argument."
)
raise EventHandlerTypeError(msg)
# The client uploadFiles handler forwards exactly the args named here,
# so it never has to know the reserved upload keys.View on GitHub (pinned to 45b8ed5ab7)
Solutions
- Convert the value to primitives (str/int/float/bool/list/dict) or its JSON equivalent before passing
- Wrap the value in a Var if it is already a computed Var
- Pass an id/key and look the object up server-side in the handler instead
Example fix
# before State.select(user) # user is a SQLAlchemy model # after State.select(user.id)
Defensive patterns
Strategy: type-guard
Validate before calling
import json
def is_event_serializable(v) -> bool:
try:
json.dumps(v, default=lambda o: getattr(o, '_var_type', None) or (_ for _ in ()).throw(TypeError))
return True
except TypeError:
return hasattr(v, '_var_type') # it's a Var Type guard
def is_event_arg(v) -> TypeGuard[object]: import json; try: json.dumps(v); return True; except TypeError: return hasattr(v, '_var_type')
Try / catch
try:
State.h(obj)
except EventHandlerTypeError:
State.h(obj.id) # fallback to identifier Prevention
- Only pass primitives, containers of primitives, or Vars to event calls
- Convert models to dicts/ids at the boundary
When it happens
Trigger: Calling State.my_event(obj) where obj is e.g. a database model, socket, PIL image, set, or custom class without a serializer.
Common situations: Passing ORM objects or numpy arrays from render code; storing non-serializable values and forwarding them to events; forgetting to extract primitive fields first.
Related errors
- `rx.EventHandler` parameters are only supported on component
- `children` in `{fn_name}` cannot be an `rx.EventHandler`; us
- `rx.EventHandler` parameter `{parameter.name}` in `{fn_name}
- @rx.event(background=True) is required for upload_files_chun
- `{handler_name}` handler should have a parameter annotated a
AI-assisted analysis of reflex-dev/reflex@45b8ed5ab7 (2026-08-28).
Data as JSON: /api/errors/5bd6bf321a90379f.
Report an issue: GitHub.