Home Features Docs Blog Philosophy Examples FAQ Live Demo Hosting
djust 1.0 is here

Django, live.
Production-ready.

Build reactive, real-time applications with Django and Python. djust 1.0 is here: a Rust VDOM for performance, resilient WebSocket recovery, accessibility in the box, and far less surface area for teams that want to ship without a JavaScript app.

djust 1.0 — now stable

djust 1.0 is here.

djust has reached a stable 1.0 contract — the public API is frozen and SemVer-stable after eighteen release candidates of hardening in production deployments. The story is unchanged: keep Django as the application model, use Rust where performance matters, and remove the frontend app unless you actually need one.

  • Framework package consolidation has shipped: auth, tenants, theming, components, and admin all live behind core extras.
  • Rust partial rendering, keyed loops, temporary assigns, streaming, server actions, and markdown streaming are all in the 1.0 line.
  • The 1.0 release cycle was intentionally boring: accessibility, recovery polish, docs, and migration sharp edges — eighteen release candidates of it.
  • Real-time community: GitHub for async, Discord for live chat — both linked in the footer.
v1.0
Stable release

API surface frozen and SemVer-stable. Shipped after eighteen release candidates of hardening in production deployments since the v0.9 series.

Rust VDOM
Sub-ms patch engine

Server-render Django templates, then let Rust diff and patch only what changed.

0 JS app
Django-native DX

No frontend build pipeline, duplicated model types, or API layer just to make a form interactive.

Redis-ready
Horizontal scale

Production state backends, reconnect recovery, and HTTP fallback for real deployments.

Philosophy

Complexity is the enemy.

djust exists for teams that want interactive software without splitting their product into two applications. Keep the domain model, forms, validation, permissions, and rendering in Django. Use Rust for the VDOM hot path. Add JavaScript only when it earns its keep.

The goal for 1.0 is not novelty. It is a smaller, boringly reliable contract for server-rendered apps: fewer moving pieces, fewer duplicated types, fewer client/server seams, and a path from static HTML to real-time collaboration that still feels like Django.

Read the full philosophy
One application model

No mandatory API layer, frontend store, schema duplication, or build pipeline just to make ordinary product workflows feel live.

Progressive by default

Start with Django templates. Layer in events, streaming, presence, optimistic updates, and server push where the interaction actually needs them.

Performance without ceremony

Rust handles diffing and patch generation so Python can stay focused on the product logic your team already understands.

The Whole Idea

How djust works

Server-rendered reactivity in plain HTML — no API, no client state.

counter/views.pyPython
from djust import LiveView
from djust.decorators import event_handler

class CounterView(LiveView):
    template_name = 'counter.html'

    def mount(self, request, **kwargs):
        self.count = 0   # state

    @event_handler()
    def increment(self, **kwargs):
        self.count += 1   # re-renders
counter.htmlTemplate
{% load djust_tags %}
<body dj-view="{{ dj_view_id }}">
  {% djust_scripts %}
  <div dj-root>   <!-- reactive region -->
    <h1>{{ count }}</h1>
    <button dj-click="increment">+</button>
  </div>
</body>

That's the whole loop. Click fires dj-click over a WebSocket, the handler mutates self.count, djust diffs the template in Rust, and only the changed bytes are patched into the page.

The directive vocabulary

Wire interactions with dj-* attributes right in your HTML. A representative dozen — there are ~40 in all.

dj-click

Fire a handler on click. data-* attrs become kwargs.

<button dj-click="save">
dj-submit

Submit a form. Named fields arrive as handler kwargs.

<form dj-submit="save">
dj-input

Fire on every keystroke. Handler gets value=.

<input dj-input="search">
dj-change

Fire on blur / select change with the current value.

<select dj-change="filter">
dj-navigate

Client-side navigation with history — no full reload.

<a dj-navigate="/app/">
dj-poll

Re-run a handler on an interval (default 5s).

<div dj-poll="refresh">
dj-debounce

Delay sending until the user pauses. Per element.

dj-debounce="300"
dj-model

Two-way binding — auto-syncs self.field.

<input dj-model="name">
dj-loading

Auto class / show / hide / disable during a round-trip.

dj-loading.disable
dj-confirm

Native confirm dialog before the event is sent.

dj-confirm="Sure?"
dj-cloak

Hide elements until the connection is live (no FOUC).

<div dj-cloak>
dj-keydown

Key events with modifiers like .enter / .escape.

dj-keydown.enter="go"
See it live — no install

Now try it in your browser.

start.djust.org is a real, multi-user djust app — click reactions, vote in a live poll, and post to a guestbook, then watch it sync for everyone at once. The Python behind each interaction is shown right on the page, so you can read the loop you just saw above as it actually runs.

Open it in two tabs and watch them update in real time — that's djust's server push, presence, and Rust-diffed patching working together, with zero frontend build.

Start your own clones the djust-start repo — a running app in under 2 minutes.

👍
Reactions

Click and watch the count jump for everyone.

📊
Live poll

Vote and see results update in real time.

📝
Guestbook

Sign it — new entries appear instantly.

State Management Primitives

Complex client-side behavior, declared in Python. djust provides a suite of decorators that handle the hard parts of frontend development for you.

@debounce

Delay server requests until the user stops typing. Perfect for search inputs.

@debounce(wait=0.5)
defsearch(self, query):
  self.results = ...
@optimistic

Update the UI instantly, validate on the server later. Zero latency feel.

@optimistic
deflike(self):
  self.liked = True
@cache

Cache responses client-side. Instant results for repeated queries.

@cache(ttl=300)
defget_cities(self):
  return Cities.all()
@client_state

Sync multiple components instantly without a server roundtrip.

@client_state(keys=['tab'])
defswitch_tab(self, tab):
  self.tab = tab
shadcn/ui for Django

Copy. Paste. Own.

Stop fighting with npm packages. djust uses a component-as-code philosophy. Copy our components into your project and customize them to your heart's content.

  • Framework AgnosticSwitch between Bootstrap 5 and Tailwind CSS with a single config setting.
  • Two-Tier ArchitectureUse lightweight Component for static UI and powerful LiveComponent for interactive widgets.
Browse Component Library
components/navbar.py
class Navbar(Component):
    def render(self):
        framework = config.get('css_framework')

        if framework == 'bootstrap5':
        elif framework == 'tailwind':
            return self._render_tailwind()

        return self._render_plain()
Performance Magic

The End of N+1 Queries.

The #1 performance killer in Django apps is the N+1 query problem. djust solves it automatically.

Our compiler analyzes your templates to see exactly which fields you use (e.g., {{ book.author.name }}). It then automatically injects the optimal select_related calls into your QuerySet.

See How It Works
Without djust
With djust
Template:
{% for book in books %}
  {{ book.author.name }}
{% endfor %}
SQL Queries:
SELECT * FROM books
SELECT * FROM authors WHERE id=1
SELECT * FROM authors WHERE id=2
... (100 more)
Template:
{% for book in books %}
  {{ book.author.name }}
{% endfor %}
SQL Queries:
SELECT * FROM books
JOIN authors ON ...
// 1 Query Total
Real-Time Built In

Everything Real-Time. Out of the Box.

Streaming, presence, server push, and data binding — all built into the framework with zero configuration.

Streaming

Stream LLM tokens, logs, or any async data directly to the browser.

self.stream_text("output", token)
Presence

Track who is online with automatic join/leave detection.

self.track_presence(user)
Server Push

Push updates from background tasks to connected clients.

push_to_view(view_id, data)
Data Binding

Two-way form binding with zero event handlers.

<input dj-model="name">
Multi-User Apps

Real-time Collaboration.
Three Lines of Python.

Presence tracking, live cursors, and broadcast updates — without WebSocket boilerplate, pub/sub clients, or frontend state machines.

Track who's online
# Mix in one class — that's it
classDocView(LiveView, PresenceMixin):
  presence_key = "doc:{doc_id}"

  defmount(self, request, **kwargs):
    self.track_presence(
      meta={"name": request.user.username}
    )

  defhandle_presence_join(self, presence):
    # fires on every tab/device — auto-deduped
    self.viewers = self.list_presences()
Broadcast to everyone→ all viewers
# One call pushes to every connected client
defsave_document(self):
  self.doc.save()
  self.broadcast_to_presence(
    "doc_updated",
    {"saved_by": self.request.user.username},
  )
Live cursorsLiveCursorMixin
# Swap PresenceMixin → LiveCursorMixin
classDocView(LiveView, LiveCursorMixin):
  ...

  defget_context_data(self, **kwargs):
    ctx = super().get_context_data(**kwargs)
    ctx["cursors"] = self.get_cursors()
    return ctx
Live in this document
A
B
C
+2
5 viewers · 3 editing
aliceediting section 2
bobviewing
carolediting section 4
vs Phoenix Presence
Same mental model

join/leave hooks, metadata, group keys — directly inspired by Phoenix Presence.

No separate frontend state

Phoenix requires JS to merge presence diffs client-side. djust handles it server-side — your template just renders presences.

Works with any backend

Memory backend for dev, Redis for prod. Swap with one settings change.

Read the Presence docs