# django-pwa turns a Django site into an installable app without an app store

> django-pwa is a MIT licensed Django application that adds a manifest.json, a service worker and a template tag, so a Django site can be installed to a home screen and run without browser chrome, with everything configured through PWA_APP_ settings.

**silviolleite/django-pwa** — Looks like an app, feels like an app, but IS NOT an app. 

- Repository: https://github.com/silviolleite/django-pwa
- Stars: 633 · Forks: 116
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/silviolleite-django-pwa

## An installable shell around an existing Django site

The project description is deliberately blunt: it looks like an app, feels like an app, but is not an app. The README puts it more precisely. This Django app turns your project into a progressive web app, and navigating to your site on an Android phone will prompt you to add the app to your home screen.

What you get on the other side of that prompt is a launch that displays your app without browser chrome. The README then draws the consequence immediately: because the application is launched this way, it is critical that your application provides all navigation within the HTML, with no reliance on the browser back or forward button.

That constraint is the single most important thing to check before adopting this package, and it is a design consequence rather than a bug. A progressive web app launched from the home screen has no address bar, no history list and no visible way back, so a Django application whose navigation depends on the browser's session history will feel broken to a user who installed it. An application built around links, forms and in-page routing will not notice the difference.

There is a second hard requirement, and it comes from the platform rather than from this package. Progressive web apps require HTTPS unless they are being served from localhost, and the README points at Let's Encrypt and ZeroSSL for teams that are not already serving over TLS. A Django site served over plain HTTP on a real domain cannot be installed, so TLS is a prerequisite rather than a recommendation.

The audience, then, is a Django team that already has a site, wants it installable on mobile, and is prepared to make its navigation self-contained.

## Everything is a setting, and the defaults are usable

The configuration surface is a list of module level settings in settings.py, and the list is long enough that it is worth seeing the shape of it rather than the individual names. The application name and description, the theme colour and the background colour, the display mode, the scope, the orientation, the start URL and the status bar colour:

```python
PWA_APP_NAME = 'My App'
PWA_APP_DESCRIPTION = "My app description"
PWA_APP_THEME_COLOR = '#0A0302'
PWA_APP_BACKGROUND_COLOR = '#ffffff'
PWA_APP_DISPLAY = 'standalone'
PWA_APP_SCOPE = '/'
PWA_APP_ORIENTATION = 'any'
PWA_APP_START_URL = '/'
PWA_APP_STATUS_BAR_COLOR = 'default'
```

Then the media. Icons come as a list of dictionaries with a source and a sizes value, and there is a separate list for Apple icons, so the iOS home screen badge is not the same file as the Android one. Splash screen images are a list with a source and a media query, which is how a single template covers several device sizes. Screenshots are the same shape with a source, a sizes value and a type. Shortcuts are a list of named links, with a name, a url and a description, and they are what the long-press menu on the installed icon shows. The direction and language of the app are set with PWA_APP_DIR and PWA_APP_LANG.

Two rules govern all of it. Every setting is optional and the app works with its internal defaults, so a project can install the package and see something on the home screen before configuring anything. And a setting can be set to an empty string or an empty list, whichever applies, in order to opt out of one of the internal defaults rather than inherit it.

The README's own recommendation is to set at least PWA_APP_NAME, PWA_APP_DESCRIPTION, PWA_APP_ICONS and PWA_APP_SPLASH_SCREEN, since those are the ones a user sees on the install prompt and on launch.

## Two wiring steps: URLs and a template tag

Once the settings are in place, the application has to be wired into the project in two places, and the README is specific about both.

The app is added to the installed applications list in settings.py, in the ordinary way:

```python
INSTALLED_APPS = [
    ...
    'pwa',
    ...
]
```

The static files directory has to be defined for the custom PWA_APP_ICONS to be found, pointing at the directory where the icons live:

```python
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
```

Then the URLs are added, and this one carries an explicit warning in the source comment. The pwa.urls module must be included with an empty string as the prefix, because the manifest, the service worker and the offline view all live at fixed paths that the browser expects to find at the root:

```python
from django.urls import url, include

urlpatterns = [
    ...
    url('', include('pwa.urls')),  # You MUST use an empty string as the URL prefix
    ...
]
```

Finally, the metadata tags are injected into the head of the base template with a template tag, which is why the template engine needs to load the pwa library first:

```html
{% load pwa %}

<head>
    ...
    {% progressive_web_app_meta %}
    ...
</head>
```

That tag is what emits the manifest link and the theme and status bar metadata, so a project that adds the app and the URLs but forgets the tag will have a working manifest and no way for the browser to learn about it.

One more setting matters during development. Setting PWA_APP_DEBUG_MODE to False disables the console.log output in the browser, which is worth knowing about because the noise appears on every page load otherwise.

## The bundled service worker caches, then serves from cache

The package ships a service worker, and the README prints it in full, which makes it easy to see exactly what the offline behaviour is. The file list is explicit: the offline view, a stylesheet, a set of icon sizes from 72x72 to 512x512, and a long list of splash screens at the sizes mobile devices ask for.

The install handler opens a cache named with a timestamp appended to a fixed prefix, and adds the whole file list to it:

```js
// Cache on install
self.addEventListener("install", event => {
    this.skipWaiting();
    event.waitUntil(
        caches.open(staticCacheName)
            .then(cache => {
                return cache.addAll(filesToCache);
            })
    )
});
```

The activate handler then deletes every other cache with the same prefix, which is the mechanism that stops the cache from growing without bound as the user visits the site over time. The name is built from a timestamp, so a new cache is created per service worker version and the old ones are removed on activation.

The fetch handler is the part that defines the offline experience, and it is short:

```js
self.addEventListener("fetch", event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                return response || fetch(event.request);
            })
            .catch(() => {
                return caches.match('offline');
            })
    )
});
```

The order is cache first, then the network, and if both fail the offline page is served. That is a cache-first strategy for every request, which is what makes a repeat visit instant, and it is also the thing to be careful about, because a cached response is preferred over a fresh one, including for HTML.

The worker is replaceable. To use your own service worker, you create a serviceworker.js or similarly named template in a template directory and point the package at it with PWA_SERVICE_WORKER_PATH, with PWA_APP_FETCH_URL passed through, and the README notes that the offline view section is where the built-in offline page is described.

## Checking it works on the Django test server

The troubleshooting section is written for the moment when the install prompt does not appear, and it is a five step checklist that a developer can run through in a couple of minutes.

The first three steps are about the three routes the application serves. Verify that /manifest.json is being served, that /serviceworker.js is being served, and that /offline is being served. Each of these being missing produces a different symptom, and the first two are exactly the routes that the URL inclusion with an empty prefix exists to provide, so a failure here usually means the URLs were included under a prefix or the static files are not where the settings say.

The fourth step is to use the Application tab in the Chrome Developer Tools to verify that the progressive web app is configured correctly. The fifth is to use the Add to homescreen link on that same tab to confirm that the install actually succeeds. Doing it inside the browser rather than on a real device is the quick check, and it is the one that catches a malformed manifest before a user ever sees it.

Two practical notes on the environment. A service worker is registered for a secure context, so the test server on localhost is one of the cases where it will work without TLS, which is why the checklist is framed around the Django test server in the first place. And the state of the browser's storage matters, since a previously installed service worker with an older file list will keep serving the older cache until it is updated, which is another reason the activation handler deletes the previous caches.

## Package state: MIT, Python 3.8 floor, recent maintenance

The package metadata describes a small, well-scoped library. The name is django-pwa, the description is a Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior, and the licence is MIT with the notice file declared in the metadata.

The supported range is wide. It requires Python 3.8 or later, with classifiers running from 3.8 through 3.14, and it depends on Django 3.2 or later, with the framework classifiers covering Django 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2 and 6.0. The dependency list is a single entry, django, which is the right shape for a package that only ships templates, settings and a URL module.

The build uses hatchling with hatch-vcs, so the version comes from the repository tags rather than from a number in the file, and the development tooling is a dependency group with pytest, pytest-django, coverage, deptry, pre-commit, faker and gunicorn. The repository also carries a ruff_defaults.toml, a .pre-commit-config.yaml, a .devcontainer directory, a testproj directory for a throwaway project used in testing, and a tests directory.

On versions, the published releases are 2.0.1 on 2024-09-27, 2.0.0 on 2024-07-18 and v1.1.0 on 2023-06-17. The last push to the repository was on 2026-08-05, and the repository is not archived, so the code is being worked on even though the last tag is nearly two years old. The 2.0 release in July 2024 is the point where a project like this would have modernised its tooling, and the metadata shows that it has.

One thing to be aware of is the tag naming. The first two releases use a plain version tag while the earlier one carries a v prefix, so a script that constructs a download URL from a tag needs to know which convention it is dealing with.

## Conclusion

django-pwa fits a Django team that wants an installable, offline-capable shell for an existing site without maintaining a native codebase or a separate front end, and who can serve the site over HTTPS. It is the wrong choice if the application depends on the browser back and forward buttons, because a launch from the home screen has no browser chrome and no history to fall back on, and it is the wrong choice if you need push notifications or background sync, since the bundled service worker only caches and serves. Before adopting it, read the bundled service worker to see exactly what it caches, decide whether your own navigation model survives the loss of browser history, and check the version you pin against the Django releases your project targets.

## FAQ

### How do I install django-pwa?

Install it from PyPI with pip install django-pwa, add 'pwa' to INSTALLED_APPS in settings.py, define STATICFILES_DIRS for your icons, and include the package URLs with an empty prefix using url('', include('pwa.urls')). The manifest and service worker metadata are injected into your base template with the progressive_web_app_meta tag after loading the pwa template library.

### Does a Django site need HTTPS to work as a progressive web app?

Yes. The README states that progressive web apps require HTTPS unless being served from localhost, and points to Let's Encrypt and ZeroSSL for teams that do not already serve over TLS.

### What is the main constraint on my Django app if I use django-pwa?

Launching from the home screen shows the app without browser chrome, so the README says it is critical that your application provides all navigation within the HTML, with no reliance on the browser back or forward button.

### Can I supply my own service worker to django-pwa?

Yes. Create a serviceworker.js or similarly named template in a template directory and point at it with PWA_SERVICE_WORKER_PATH, with PWA_APP_FETCH_URL passed through. The bundled worker caches the offline view, icons and splash screens, serves from cache first, and falls back to the offline page.

### Which Django and Python versions does django-pwa support?

The package requires Python 3.8 or later and Django 3.2 or later, with the metadata classifiers covering Python 3.8 through 3.14 and Django 3.2 through 6.0. The published releases are 2.0.1 on 2024-09-27 and 2.0.0 on 2024-07-18.

## Sources

- [Issues](https://github.com/silviolleite/django-pwa/issues)
- [License: MIT](https://github.com/silviolleite/django-pwa/blob/master/LICENSE)
- [README](https://github.com/silviolleite/django-pwa/blob/master/README.md)
- [Releases](https://github.com/silviolleite/django-pwa/releases)
- [silviolleite/django-pwa on GitHub](https://github.com/silviolleite/django-pwa)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/silviolleite-django-pwa
