Home Features Docs Blog Philosophy Examples FAQ Live Demo Hosting
API Reference

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 RPC

Decorators 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

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.

Python
@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

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

Python
@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

HTML
{% 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

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

Python
@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

JavaScript
const hits = await djust.call('app.ProductView', 'search', {q: 'laptop'});

Reactivity

Derived and auto-rerendering state

Declare 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

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.

Python
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.
# @state

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.

Python
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

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

Python
@computed
def doubled(self):
    return self.count * 2

Memoized

Python
@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 UI

Tune 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

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.

Python
@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

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.

Python
@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.

Python
@optimistic
@event_handler()
def increment(self, **kwargs):
    self.count += 1
@cache Cache handler responses client-side with a TTL.
# @cache

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.

Python
@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

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.

Python
@client_state(keys=['tab'])
@event_handler()
def switch_tab(self, tab: str = "", **kwargs):
    self.tab = tab

Access & execution

Permissions, rate limits, background work

Gate 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

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.

Python
@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

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.

Python
@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.

Python
@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

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.

Python
@on_mount
def require_verified(view, request, **kwargs):
    if not request.user.email_verified:
        return '/verify-email/'

class ProfileView(LiveView):
    on_mount = [require_verified]