Library / SDK
adamchainz/django-browser-reload avatar
adamchainz/django-browser-reload

django-browser-reload: automatic refresh on Django code, template and static changes

Automatically reload your browser in development.

673 stars35 forksPythonMIT

At a glance

What is it?
django-browser-reload injects a script into HTML responses that listens on an EventSource and reloads the page when runserver restarts. It supports Python 3.10 to 3.15 and Django 5.2 to 6.1, and it can be limited to specific pages through a template tag.
Who is it for?
django-browser-reload fits Django projects on runserver where the developer wants the tab to refresh on a template or static file edit without reaching for the browser, and it works with both WSGI and ASGI servers. It does not fit a team that reloads through a separate process manager, a container rebuild, or a framework server without autoreload, since the events are triggered by runserver restarts.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The mechanism is a script tag and an EventSource, not a websocket

The way this package works is described plainly in the documentation. It adds a script tag into HTML responses, just before the closing body tag. That script connects back to the development server and receives events that tell it when to reload, and those events are triggered through server restarts and the autoreload system of runserver.

That is the whole design, and it is worth understanding what it depends on. The refresh is not pushed by a file watcher belonging to the package; it is a side effect of the server restarting, exactly as it would be if you pressed a reload key yourself. The difference is that the browser does the pressing for you.

The browser side has two requirements. EventSource is described as universally available. SharedWorker is described as available on Chrome, Edge, Firefox and Opera for a long time, and on Safari since version 16, dated 2022-09-12 in the documentation. The SharedWorker requirement is what lets a single background worker serve several tabs of the same site, and it is the reason the documentation notes that if you open multiple tabs, only the most recently used tab will reload.

Version support is narrow and stated up front: Python 3.10 to 3.15, and Django 5.2 to 6.1. Both WSGI and ASGI are supported.

Four configuration steps, and the middleware order decides whether it works at all

Installation is a pip install followed by three edits to your project configuration.

The install itself:

bash
       python -m pip install django-browser-reload

The app has to be in INSTALLED_APPS, and the documentation asks that django.contrib.staticfiles is also present there:

python
       INSTALLED_APPS = [
           ...,
           "django_browser_reload",
           ...,
       ]

Then the URLs are included in the root URLconf:

python
       from django.urls import include, path

       urlpatterns = [
           ...,
           path("__reload__/", include("django_browser_reload.urls")),
       ]

The prefix can be changed if you need another one, and the documentation says so explicitly after the snippet.

Finally the middleware:

python
      MIDDLEWARE = [
          # ...
          "django_browser_reload.middleware.BrowserReloadMiddleware",
          # ...
      ]

The ordering constraint is the detail that bites people. The middleware should be listed after any others that encode the response, such as the GZipMiddleware that ships with Django. Put it earlier and the response gets compressed before the script tag is added, so the browser never receives the tag and nothing appears to happen.

Once configured, you run runserver with DEBUG set to True and open the site. Changes to Python code, templates or static assets then trigger the reload automatically.

The middleware injects into every HTML response, the template tag does not

The middleware automatically inserts the required script tag on HTML responses before the closing body tag when DEBUG is True. The documentation is explicit about the side effect: it does so to every HTML response, meaning the tag will be included on the Django debug pages, the admin pages and the rest of them.

That is convenient in the common case and a problem in specific ones. If your pages are wrapped in a strict Content Security Policy, an unconditional script tag is exactly the kind of thing such a policy is designed to block, and a page that suddenly stops reloading because a policy was tightened elsewhere is a confusing symptom.

The template tag is the escape route. You can insert the script on relevant pages instead of using the middleware, which the documentation says may be useful if you want to restrict reloading to certain pages, or if the middleware does not work for your page structure. The tag has both Django template and Jinja versions, and it only outputs the script tag when DEBUG is True.

For Django Templates, you load the tag and place it in your base template, ideally just before the closing body tag:

html
   {% load django_browser_reload %}

   ...

       {% django_browser_reload_script %}
     </body>
   </html>

For Jinja templates there are two steps, because Jinja has no Django style load tag. The function has to be registered in the environment globals of a custom Jinja environment, and then it is called in the base template with the django_browser_reload_script() function. The documentation also shows how to add the tag inside the Django admin, by overriding the admin/base_site.html template and placing the tag inside an extrahead block.

Content Security Policy nonces are handled on Django 6.0 and later

On Django 6.0 and later with ContentSecurityPolicyMiddleware, the documentation states that the script tag will include the Content Security Policy nonce. This applies to both the middleware and the template tag, and for the Jinja version you pass it in explicitly:

jinja
    {{ django_browser_reload_script(nonce=csp_nonce) }}

For the Django template version, the nonce is taken from the context if it is present there, and the documentation states that the tag includes the nonce if it is in the context.

This is the difference between the template tag and the middleware under a strict policy, and it is the strongest argument for the tag over the middleware. A nonce is per-response, and a static script tag has no way to carry one. The middleware can pick the nonce up from the response context, but the moment your page structure stops matching what the middleware expects, or you want reloading restricted to a few pages, the tag is the mechanism that still works.

One caveat applies to the version range. The nonce behaviour is described for Django 6.0 and later, while the package also supports Django 5.2. On the older branch the nonce feature is simply not part of the picture, so a strict policy there needs a different plan.

uv_build packaging, two runtime dependencies and a wide test matrix

The project metadata gives a fairly modern picture of the tooling. The build backend is uv_build, requiring uv-build 0.12.1 or newer within the 0.13 series, and the version is 1.21.0 with the description Automatically refresh your browser on changes to Python code, templates, or static files. The licence is MIT, with the LICENSE file listed under license-files, and the author is Adam Johnson.

The runtime dependencies are small: asgiref 3.6 or newer, and django 5.2 or newer. The asgiref dependency is what allows the package to work under both WSGI and ASGI, and it is the only dependency beyond Django itself.

The test matrix is more interesting than the runtime dependencies. The dependency groups define django52 pinned to Django 5.2 alpha releases with a Python 3.10 floor, django60 for 6.0 alphas with Python 3.12, and django61 for 6.1 alphas with Python 3.12, and the tool.uv section declares these groups as conflicting with one another so they cannot be installed together. The test group pulls in coverage with TOML support, jinja2, pytest 9 or newer, pytest-django and pytest-randomly, and the example group adds daphne, jinja2 and pywatchman, which is what lets the example project run under an ASGI server.

The example directory in the repository is the intended way to see the package handle different asset types, and the documentation says to open the example/ directory and follow the instructions in its own README. The pywatchman dependency and the recommendation to install django-watchfiles alongside point at a different approach to change detection, which is a sensible pairing if the file watching built into runserver is slow on your filesystem.

The repository also carries a HISTORY.rst and a CHANGELOG.rst, and the metadata links the changelog URL for anyone who wants the release history without searching the site.

Editorial conclusion

django-browser-reload fits Django projects on runserver where the developer wants the tab to refresh on a template or static file edit without reaching for the browser, and it works with both WSGI and ASGI servers. It does not fit a team that reloads through a separate process manager, a container rebuild, or a framework server without autoreload, since the events are triggered by runserver restarts. Before adopting it, check the middleware ordering against GZipMiddleware as the documentation requires, confirm your browser supports SharedWorker, which Safari only gained in version 16, and if you use Django 6.0 or later with ContentSecurityPolicyMiddleware verify the nonce reaches the injected script tag.

Frequently asked questions

How do I install django-browser-reload?

Run python -m pip install django-browser-reload, add django_browser_reload to INSTALLED_APPS alongside django.contrib.staticfiles, include the app URLs in your root URLconf, and add django_browser_reload.middleware.BrowserReloadMiddleware to MIDDLEWARE.

Why is my browser not reloading after installing django-browser-reload?

The middleware must be listed after any middleware that encodes the response, such as GZipMiddleware, otherwise the script tag is added to a response that is then compressed and never reaches the browser as plain HTML. Also confirm DEBUG is True and that runserver is the server in use, since the reload events are triggered by server restarts.

Can I restrict django-browser-reload to certain pages?

Yes. Use the template tag instead of the middleware, in Django templates with the django_browser_reload_script tag or in Jinja templates with the django_browser_reload_script() call, and it only outputs the script tag when DEBUG is True.

Does django-browser-reload work with a Content Security Policy?

On Django 6.0 and later with ContentSecurityPolicyMiddleware, the script tag includes the Content Security Policy nonce, and for the Jinja version you pass it directly with django_browser_reload_script(nonce=csp_nonce).

Which browsers and versions does django-browser-reload need?

Your browser needs EventSource, described as universally available, and SharedWorker, which is available on Chrome, Edge, Firefox and Opera for a long time and on Safari since version 16.

Official sources

  1. adamchainz/django-browser-reload on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/adamchainz-django-browser-reload.svg)](https://hysenlabs.com/projects/adamchainz-django-browser-reload)

Was this useful?