Decorator reference
All 15@ decorators for handlers, reactivity, and client-side behaviour.
Decorators turn ordinary methods into framework hooks. @event_handler is the one you'll reach for constantly — it exposes a method to dj-* events; the rest tune reactivity, client-side behaviour (@debounce, @cache, @optimistic), access control, and background execution. Stack them above any method on a LiveView.
Event handling
Expose methods to events, actions, and RPCDecorators that expose methods to events. @event_handler is required for any method a dj-* event calls; @action adds automatic pending/error/result tracking; @server_function turns a method into same-origin RPC the browser can call without a re-render.
@event_handler
Mark a method as a WebSocket event handler. @event_handler(params=None, description='', coerce_types=True, expose_api=False, serialize=None)
Signature
@event_handler(params=None, description='', coerce_types=True, expose_api=False, serialize=None)
Effect
Registers a validated WS handler
The required decorator for any method bound to a dj-* event. It introspects the signature to coerce string params to typed kwargs (item_id: int gets "5" as 5). Pass expose_api=True to also mount an HTTP POST endpoint. Cannot be combined with @server_function.
@event_handler()
def search(self, value: str = "", **kwargs):
self.results = Product.objects.filter(name__icontains=value)
@action
Handler with auto pending / error / result state. @action(description='', coerce_types=True)
Signature
@action(description='', coerce_types=True)
Effect
Tracks pending/error/result automatically
Builds on @event_handler to track state in self._action_state[name]: pending=True on entry, result on success, error on exception (logged, not re-raised). Templates read {{ create_todo.pending }} etc. React-19-style server actions.
Action
@action()
def create_todo(self, title: str = "", **kwargs):
if not title:
raise ValueError("Title required")
return {"id": Todo.objects.create(title=title).id}
Template state
{% if create_todo.pending %}Creating…
{% elif create_todo.error %}{{ create_todo.error }}
{% elif create_todo.result %}Created #{{ create_todo.result.id }}{% endif %}
@server_function
Same-origin browser RPC target — no re-render. @server_function(description='', coerce_types=True)
Signature
@server_function(description='', coerce_types=True)
Effect
Callable via djust.call(); JSON response
Call from JS as await djust.call('<view>', '<name>', {params}). Returns JSON with no VDOM re-render — ideal for autocomplete or lookups. Reuses param validation, @permission_required, and @rate_limit. Session-cookie auth + CSRF required. Cannot combine with @event_handler.
Server side
@server_function()
def search(self, q: str = "", **kwargs) -> list[dict]:
return [{"id": p.id, "name": p.name}
for p in Product.objects.filter(name__icontains=q)[:10]]
Client call
const hits = await djust.call('app.ProductView', 'search', {q: 'laptop'});
Reactivity
Derived and auto-rerendering stateDeclare derived and auto-tracking state. @state declares a reactive attribute, @reactive makes a property re-render on change, and @computed memoizes a value derived from dependencies — recomputing only on changes.
@reactive
Property that re-renders when its value changes. @reactive (with optional .setter)
Signature
@reactive (with optional .setter)
Effect
Setter calls self.update() on change
A descriptor-based reactive property. When the value changes, it automatically queues a re-render via self.update(). Use the .setter form to add custom assignment logic.
class MyView(LiveView):
@reactive
def count(self):
return self._count
@count.setter
def count(self, value):
self._count = value # re-renders on change
@state
Declare reactive state with a default value. name = state(default=None)
Signature
name = state(default=None)
Effect
Class-level reactive attribute
A cleaner alternative to initialising attributes in mount(). The attribute is reactive (mutating it re-renders) and is included in the template context automatically.
class TodoView(LiveView):
count = state(default=0)
@event_handler()
def increment(self, **kwargs):
self.count += 1 # triggers re-render
@computed
Derived property, optionally memoized on deps. @computed or @computed('dep1', 'dep2')
Signature
@computed or @computed('dep1', 'dep2')
Effect
Recomputes (or caches) a derived value
Plain @computed recomputes on every access (cheap derivations). The memoized form @computed('items', 'tax_rate') caches until the named dependencies change (by fingerprint). Thread-safe.
Plain
@computed
def doubled(self):
return self.count * 2
Memoized
@computed('items', 'tax_rate')
def total(self):
sub = sum(i['price'] * i['qty'] for i in self.items)
return sub * (1 + self.tax_rate)
Client-side behavior
Debounce, throttle, cache, optimistic UITune how events behave on the client before reaching the server. @debounce and @throttle rate-limit chatty events; @optimistic updates the UI instantly and reconciles later; @cache stores responses with a TTL; @client_state shares state across components via the client StateBus.
@debounce
Wait for a pause before sending the event. @debounce(wait=0.3, max_wait=None)
Signature
@debounce(wait=0.3, max_wait=None)
Effect
Client waits idle period before dispatch
Client-side metadata: after an event fires the client waits `wait` seconds; a new event resets the timer. max_wait forces a dispatch if the idle period keeps extending. Ideal for live search inputs.
@debounce(wait=0.5)
@event_handler()
def search(self, value: str = "", **kwargs):
self.results = Product.objects.filter(name__icontains=value)
@throttle
Limit handler to at most once per interval. @throttle(interval=0.1, leading=True, trailing=True)
Signature
@throttle(interval=0.1, leading=True, trailing=True)
Effect
Client caps dispatch frequency
For scroll / resize / mousemove. leading fires immediately on the first event; trailing fires once more after the interval if events kept arriving. Both can be combined.
@throttle(interval=0.1)
@event_handler()
def on_scroll(self, scroll_y: int = 0, **kwargs):
self.scroll_position = scroll_y
@optimistic
Apply the UI update instantly, correct on reply. @optimistic
Signature
@optimistic
Effect
Client updates UI before the round-trip
Signals the client to apply the event optimistically for zero-latency feel; the server's response patches any mismatch. Best for toggles and counters where the result is predictable.
@optimistic
@event_handler()
def increment(self, **kwargs):
self.count += 1
@cache
Cache handler responses client-side with a TTL. @cache(ttl=60, key_params=None)
Signature
@cache(ttl=60, key_params=None)
Effect
Client caches the response for ttl seconds
Client-side cache keyed by handler name plus the named key_params. With key_params=['query'] the key is search:laptop. Omit key_params for a handler-name-only cache. Instant repeat results.
@cache(ttl=60, key_params=['query'])
@event_handler()
def search(self, query: str = "", **kwargs):
self.results = Product.objects.filter(name__icontains=query)
@client_state
Share state across handlers via a client StateBus. @client_state(keys=['tab'])
Signature
@client_state(keys=['tab'])
Effect
Publishes keys on the client StateBus
Enables cross-component coordination without a server round-trip: when the handler runs, the named keys are published on the client and other subscribed handlers are notified. Pub/sub on the client.
@client_state(keys=['tab'])
@event_handler()
def switch_tab(self, tab: str = "", **kwargs):
self.tab = tab
Access & execution
Permissions, rate limits, background workGate and schedule handler execution. @permission_required and @rate_limit enforce access and throughput server-side; @background runs a handler after flushing the UI so slow work doesn't block the response; @on_mount registers cross-cutting logic before mount().
@permission_required
Require Django permission(s) to run the handler. @permission_required(perm)
Signature
@permission_required(perm)
Effect
Rejects the event if the user lacks perm
Checked server-side before the handler runs. Accepts a single permission string or a list. Composes with @event_handler / @server_function and the @rate_limit token bucket.
@permission_required('myapp.delete_item')
@event_handler()
def delete_item(self, item_id: int = 0, **kwargs):
Item.objects.filter(id=item_id).delete()
@rate_limit
Token-bucket rate-limit a handler server-side. @rate_limit(rate=10, burst=5)
Signature
@rate_limit(rate=10, burst=5)
Effect
Drops events past the limit + warns client
Per-handler token bucket: rate is sustained events/sec, burst is the bucket capacity. When exceeded the event is dropped and the client is warned. Protects expensive handlers.
@rate_limit(rate=5, burst=3)
@event_handler()
def expensive(self, **kwargs):
self.results = do_expensive_work()
@background
Run the handler in the background after flushing. @background
Signature
@background
Effect
Flushes UI, then runs async
Wraps start_async(): state set before the heavy work (e.g. self.loading=True) flushes to the client immediately, then the body runs in the background and re-renders on completion. Works with sync and async handlers.
@event_handler()
@background
def generate(self, prompt: str = "", **kwargs):
self.generating = True
self.content = call_llm(prompt) # slow
self.generating = False
@on_mount
Cross-cutting hook that runs before mount(). @on_mount def hook(view, request, **kwargs)
Signature
@on_mount def hook(view, request, **kwargs)
Effect
Runs each mount; may redirect
Return None to continue or a URL string to halt the mount and navigate away. Hooks run in MRO order, parent-first, deduplicated. Equivalent to Phoenix's on_mount/1. Authentication runs before hooks.
@on_mount
def require_verified(view, request, **kwargs):
if not request.user.email_verified:
return '/verify-email/'
class ProfileView(LiveView):
on_mount = [require_verified]