Inertia.js Django Adapter
Official Docs and installation instructions are available here
Installation
Backend
Install the following python package via pip
pip install inertia-django
Add the Inertia app to your INSTALLED_APPS in settings.py
INSTALLED_APPS = [
# django apps,
'inertia',
# your project's apps,
]
Add the Inertia middleware to your MIDDLEWARE in settings.py
MIDDLEWARE = [
# django middleware,
'inertia.middleware.InertiaMiddleware',
# your project's middleware,
]
Finally, create a layout which exposes {% block inertia %}{% endblock %} in the body and set the path to this layout as INERTIA_LAYOUT in your settings.py file.
Now you're all set!
Frontend
Django specific frontend docs coming soon. For now, we recommend installing django_vite
and following the commits on the Django Vite example repo. Once Vite is setup with
your frontend of choice, just replace the contents of entry.js with this file (example in react)
You can also check out the official Inertia docs at https://inertiajs.com/.
CSRF
Django's CSRF tokens are tightly coupled with rendering templates, so Inertia Django automatically handles adding the CSRF cookie to every response — including non-visit XHR requests made via the useHttp hook introduced in Inertia v3.
The Inertia v3 HTTP client defaults to Laravel's XSRF-TOKEN cookie and X-XSRF-TOKEN header, while Django defaults to csrftoken and X-CSRFToken. Configure the client with Django's names (or use your project's custom CSRF names):
createInertiaApp({
// ...
http: {
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
},
})
Upgrading to Inertia v3
See the Inertia v3 upgrade guide for the required client, template, partial reload, flash, redirect, and cache changes.
Usage
Responses
Render Inertia responses is simple, you can either use the provided inertia render function or, for the most common use case, the inertia decorator. The render function accepts four arguments, the first is your request object. The second is the name of the component you want to render from within your pages directory (without extension). The third argument is a dict of props that should be provided to your components. The final argument is template_data, for any variables you want to provide to your template, but this is much less common.
from inertia import render
from .models import Event
def index(request):
return render(request, 'Event/Index', props={
'events': Event.objects.all()
})
Or use the simpler decorator for the most common use cases
from inertia import inertia
from .models import Event
@inertia('Event/Index')
def index(request):
return {
'events': Event.objects.all(),
}
If you need more control, you can also directly return the InertiaResponse class. It has the same arguments as the render method and subclasses HttpResponse to accept of all its arguments as well.
from inertia import InertiaResponse
from .models import Event
def index(request):
return InertiaResponse(
request,
'Event/Index',
props={
'events': Event.objects.all()
}
)
Shared Data
If you have data that you want to be provided as a prop to every component (a common use-case is information about the authenticated user) you can use the share method. A common place to put this would be in some custom middleware.
from inertia import share
from django.conf import settings
from .models import User
def inertia_share(get_response):
def middleware(request):
share(request,
app_name=settings.APP_NAME,
user_count=lambda: User.objects.count(), # evaluated lazily at render time
user=lambda: request.user, # evaluated lazily at render time
)
return get_response(request)
return middleware
Prop Serialization
Unlike Rails and Laravel, Django does not handle converting objects to JSON by default so Inertia Django offers two different ways to handle prop serialization.
InertiaJsonEncoder
The default behavior is via the InertiaJsonEncoder. The InertiaJsonEncoder is a barebones implementation
that extends the DjangoJSONEncoder with the ability to handle QuerySets and models. Models are JSON encoded
via Django's model_to_dict method excluding the field password. This method has limitations though, as
model_to_dict does not include fields where editable=False (such as automatic timestamps).
InertiaMeta
Starting in Inertia Django v1.2, Inertia Django supports an InertiaMeta nested class. Similar to Django Rest Framework's serializers, any class (not just models) can contain an InertiaMeta class which can specify how that class should be serialized to JSON. At this time, in only supports fields, but this may be extended in future versions.
class User(models.Model):
name = models.CharField(max_length=255)
password = models.CharField(max_length=255)
created_at = models.DateField(auto_now_add=True)
class InertiaMeta:
fields = ('name', 'created_at')
External Redirects
It is possible to redirect to an external website, or even another non-Inertia endpoint in your app while handling an Inertia request.
This can be accomplished using a server-side initiated window.location visit via the location method:
from inertia import location
def external():
return location("http://foobar.com/")
It will generate a 409 Conflict response and include the destination URL in the X-Inertia-Location header.
When this response is received client-side, Inertia will automatically perform a window.location = url visit.
Optional Props
On the front end, Inertia supports the concept of "partial reloads" where only the props requested
are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the intial load. In this case, you can use Optional props. Optional props aren't evaluated unless they're specifically requested by name in a partial reload.
from inertia import optional, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon', # this will be rendered on the first load as usual
'data': optional(lambda: some_long_calculation()), # this will only be run when specifically requested by partial props and WILL NOT be included on the initial load
}
Use always() for data that must be included even when the client requests a different subset of props:
from inertia import always
return {
'currentUser': always(lambda: request.user.username),
'reports': optional(load_reports),
}
Deferred Props
As of version 2.0, Inertia supports the ability to defer the fetching of props until after the page has been initially rendered. Essentially this is similar to the concept of Optional props however Inertia provides convenient frontend components to automatically fetch the deferred props after the page has initially loaded, instead of requiring the user to initiate a reload. For more info, see Deferred props in the Inertia documentation.
To mark props as deferred on the server side use the defer function.
from inertia import defer, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon', # this will be rendered on the first load as usual
'data': defer(lambda: some_long_calculation()), # this will only be run after the frontend has initially loaded and inertia requests this prop
}
Grouping requests
By default, all deferred props get fetched in one request after the initial page is rendered, but you can choose to fetch data in parallel by grouping props together.
from inertia import defer, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon', # this will be rendered on the first load as usual
'data': defer(lambda: some_long_calculation()),
'data1': defer(lambda: some_long_calculation1(), group='group'),
'data2': defer(lambda: some_long_calculation1(), 'group'),
}
In the example above, the data1, and data2 props will be fetched in one request, while the data prop will be fetched in a separate request in parallel. Group names are arbitrary strings and can be anything you choose.
Merge Props
By default, Inertia overwrites props with the same name when reloading a page. However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. In these cases, you can merge props instead of overwriting them.
from inertia import merge, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon',
'data': merge(Paginator(objects, 3)),
}
You can also combine deferred props with mergeable props to defer the loading of the prop and ultimately mark it as mergeable once it's loaded.
from inertia import defer, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon',
'data': defer(lambda: Paginator(objects, 3), merge=True),
}
For Inertia v3, merge() can target nested arrays and match incoming records by a stable key. deep_merge() merges nested objects and arrays recursively:
from inertia import deep_merge, merge
return {
'feed': merge(load_feed, append='items', match_on='items.id'),
'chat': deep_merge(load_chat, match_on='messages.id'),
}
Scroll Props
scroll() adds the pagination metadata used by Inertia v3's infinite-scroll components. Pass defer=True to load its first page after the initial render.
from inertia import scroll
return {
'players': scroll(
load_players,
{'pageName': 'page', 'previousPage': None, 'nextPage': 2, 'currentPage': 1},
defer=True,
),
}
Once Props
Some data rarely changes, is expensive to compute, or is simply large. Rather than sending it on every response, you can use once props. Once props are resolved on the first visit and remembered by the client. On subsequent visits the server skips re-resolving them, saving both CPU and bandwidth.
from inertia import once, inertia
@inertia('ExampleComponent')
def example(request):
return {
'name': lambda: 'Brandon',
'plans': once(lambda: Plan.objects.all()), # resolved once, then cached client-side
}
Pass fresh=True to force re-resolution on every request, regardless of whether the client already holds the value:
@inertia('Billing/Plans')
def plans(request):
return {
'plans': once(lambda: Plan.objects.all(), fresh=True),
}
Once props also work in shared data:
share(request, countries=once(lambda: list(Country.objects.values('code', 'name'))))
Flash Data
Django messages are automatically exposed through the page object's top-level flash.messages field.
from django.contrib import messages
messages.success(request, 'Brandon scored!')
return redirect('players:index')
Preserve Fragment
When a user visits a URL with a fragment (e.g. /article/old-slug#section) and the server redirects to a different URL, the fragment is normally lost. Call preserve_fragment() before returning the redirect to carry the fragment to the new URL:
from django.shortcuts import redirect
from inertia import preserve_fragment
def rename_article(request, slug):
article = Article.objects.get(slug=slug)
article.slug = request.POST['new_slug']
article.save()
preserve_fragment(request)
return redirect('articles:show', slug=article.slug)
The client will navigate to /article/new-slug#section instead of /article/new-slug.
Json Encoding
Inertia Django ships with a custom JsonEncoder at inertia.utils.InertiaJsonEncoder that extends Django's
DjangoJSONEncoder with additional logic to handle encoding models and Querysets. If you have other json
encoding logic you'd prefer, you can set a new JsonEncoder via the settings.
History Encryption
Inertia.js supports history encryption to protect sensitive data in the browser's history state. This is useful when your pages contain sensitive information that shouldn't be stored in plain text in the browser's history. This feature requires HTTPS since it relies on window.crypto.subtle which is only available in secure contexts.
You can enable history encryption globally via the INERTIA_ENCRYPT_HISTORY setting in your settings.py:
INERTIA_ENCRYPT_HISTORY = True
For more granular control, you can enable encryption on specific views:
from inertia import encrypt_history, inertia
@inertia('TestComponent')
def encrypt_history_test(request):
encrypt_history(request)
return {}
# If you have INERTIA_ENCRYPT_HISTORY = True but want to disable encryption for specific views:
@inertia('PublicComponent')
def public_view(request):
encrypt_history(request, False) # Explicitly disable encryption for this view
return {}
When users log out, you might want to clear the history to ensure no sensitive data can be accessed. You can do this by extending the logout view:
from inertia import clear_history
from django.contrib.auth import views as auth_views
class LogoutView(auth_views.LogoutView):
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
clear_history(request)
return response
SSR
The SSR is handled by a separate Node server. Your backend acts like a proxy, receiving requests from the frontend, calling the Node server (SSR server), and returning the rendered HTML to the frontend.
Look at the examples section to see working examples of SSR with Django and React or Svelte.
Backend
- Ensure
requestsis installed, so inertia-django can do SSR requests.requestsis configured as a dependency if you install the[ssr]extra, e.g.inertia-django[ssr]in your requirements.
- Enable SSR via the
INERTIA_SSR_URLandINERTIA_SSR_ENABLEDsettings.
Frontend
Follow the current Inertiajs docs for setting up SSR.
Settings
Inertia Django has a few different settings options that can be set from within your project's settings.py file. Some of them have defaults.
The default config is shown below
INERTIA_VERSION = '1.0' # defaults to '1.0'
INERTIA_LAYOUT = 'layout.html' # required and has no default
INERTIA_JSON_ENCODER = CustomJsonEncoder # defaults to inertia.utils.InertiaJsonEncoder
INERTIA_SSR_URL = 'http://localhost:13714' # defaults to http://localhost:13714
INERTIA_SSR_ENABLED = False # defaults to False
INERTIA_ENCRYPT_HISTORY = False # defaults to False
Testing
Inertia Django ships with a custom TestCase to give you some nice helper methods and assertions.
To use it, just make sure your TestCase inherits from InertiaTestCase. InertiaTestCase inherits from Django's django.test.TestCase so it includes transaction support and a client.
from inertia.test import InertiaTestCase
class ExampleTestCase(InertiaTestCase):
def test_show_assertions(self):
self.client.get('/events/')
# check the component
self.assertComponentUsed('Event/Index')
# access the component name
self.assertEqual(self.component(), 'Event/Index')
# props (including shared props)
self.assertHasExactProps({name: 'Brandon', sport: 'hockey'})
self.assertIncludesProps({sport: 'hockey'})
# access props
self.assertEquals(self.props()['name'], 'Brandon')
# template data
self.assertHasExactTemplateData({name: 'Brian', sport: 'basketball'})
self.assertIncludesTemplateData({sport: 'basketball'})
# access template data
self.assertEquals(self.template_data()['name'], 'Brian')
The inertia test helper also includes a special inertia client that pre-sets the inertia headers
for you to simulate an inertia response. You can access and use it just like the normal client with commands like self.inertia.get('/events/'). When using the inertia client, inertia custom assertions are not enabled though, so only use it if you want to directly assert against the json response.
Examples
- Django Svelte Template - A Django template and example project demonstrating Inertia with Svelte and SSR.
- Django React: A Django + React project including CRUD operations, form handling, authentication, deployment using Docker, SSR, and more.
Thank you
A huge thank you to the community members who have worked on InertiaJS for Django before us. Parts of this repo were particularly inspired by Andres Vargas and Samuel Girardin. Additional thanks to Andres for the Pypi project.
Release files for inertia-django 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| inertia_django-2.0.0.tar.gz | 62.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| inertia_django-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 135.4 kB
Release files / inertia_django-2.0.0.tar.gz
| Download URL | inertia_django-2.0.0.tar.gz |
|---|---|
| Size | 62.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
89ab1ea1a1f45c3441010ecb3d0bca0b0256404d1b1f84e1f1abd734e21006f5
|
|
BLAKE2b-256 checksum How to use checksums |
51aeabfa389f0944526a813e9c3ab644de091b22c7cf8b5284f4381e066509db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.
Transparency logRelease files / inertia_django-2.0.0-py3-none-any.whl
| Download URL | inertia_django-2.0.0-py3-none-any.whl |
|---|---|
| Size | 73.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d8d2200b0d273c39939015d9a2a42b69b7744e2e3b2a9a875bc9fa81df90e73
|
|
BLAKE2b-256 checksum How to use checksums |
ab6a1c28b9b189b1dc5767b34165b01f65ef46aaab6a38ccc5254a0e3717ccf2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.
Transparency log