Skip to main content

image

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 requests is installed, so inertia-django can do SSR requests.
    • requests is configured as a dependency if you install the [ssr] extra, e.g. inertia-django[ssr] in your requirements.
  • Enable SSR via the INERTIA_SSR_URL and INERTIA_SSR_ENABLED settings.

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)

Source distribution for inertia-django 2.0.0
File Size Uploaded
inertia_django-2.0.0.tar.gz 62.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for inertia-django 2.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.6.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

1 release file

0.3.0

1 release file

0.2.7

1 release file

0.2.6

1 release file

0.2.5

1 release file

0.2.4

1 release file

0.2.3

1 release file

0.2.2

2 release files

0.2.1

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page