A simple, standalone template tag for loading webpack manifest files from multiple Django packages/apps. Zero dependencies on unmaintained packages.
When using webpack with multiple Django packages (like righttowork-check, criminalrecords-check, etc.), each package generates its own manifest.json file with hashed asset filenames for cache busting. However, most manifest loaders only read a single manifest file, making it impossible to reference assets from installed packages.
django-multi-manifest-loader provides a standalone template tag that:
- Loads the main webpack manifest (from your dashboard/main app)
- Automatically discovers and loads manifest files from all installed Django packages
- Namespaces manifests by app to prevent naming collisions
- Auto-detects the current app from template context for seamless usage
- Makes all assets available via the
{% manifest %}template tag - Zero dependencies - doesn't rely on unmaintained packages like
django-manifest-loader
- Automatic Namespacing: Each app's manifest is namespaced, so
config.jsinpackage1doesn't conflict withconfig.jsinpackage2 - Smart Auto-Detection: Automatically uses the correct app's assets based on which template is rendering
- Explicit App Parameter: Use
app='appname'parameter when you need assets from a specific app - Fallback to Main: If an asset isn't found in the current app, falls back to the main app's manifest
pip install django-multi-manifest-loaderOr install from source:
cd django-multi-manifest-loader
pip install -e .In your settings.py:
INSTALLED_APPS = [
# ...
'django_multi_manifest_loader',
# ...
]In your settings.py:
DJANGO_MULTI_MANIFEST_LOADER = {
'cache': True, # Enable caching (default: True in production, False in DEBUG)
'main_app_name': 'dashboard', # Name for main app manifests (default: 'main')
}The manifest tag automatically detects which app's template is rendering and loads the correct assets:
{% load manifest %}
{# In package1/templates/package1/form.html #}
{# Automatically uses package1's manifest #}
<script src="{% manifest 'config.js' %}"></script>
{# Returns: package1/js/config.abc123.js #}
{# In package2/templates/package2/wizard.html #}
{# Automatically uses package2's manifest #}
<script src="{% manifest 'config.js' %}"></script>
{# Returns: package2/js/config.xyz789.js #}
{# Explicit app parameter - use assets from another app #}
<script src="{% manifest 'main.js' app='dashboard' %}"></script>
{# Always returns dashboard's main.js #}
{# If not found in current app, falls back to main app #}
<link href="{% manifest 'shared-styles.css' %}" rel="stylesheet">The loader searches for manifest files in all installed Django apps and namespaces them by app:
- Discovery: Finds
manifest.jsonfiles in all Django apps using staticfiles finders - Namespacing: Each app's manifest is stored under its app name (e.g.,
package1,package2) - Auto-Detection: When rendering a template, extracts the app name from the template path (e.g.,
package1/form.html→package1) - Resolution:
- First tries the current app's manifest
- Falls back to the main app if not found
- Supports explicit
app='appname'parameter for cross-app asset references
Result: No naming collisions! Each app's config.js is kept separate.
In your Django package (e.g., righttowork-check), generate a manifest using webpack:
webpack.config.js:
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
module.exports = {
output: {
path: path.resolve(__dirname, 'static/righttoworkcheck/js/'),
filename: '[name].[contenthash:8].js',
publicPath: 'righttoworkcheck/js/'
},
plugins: [
new WebpackManifestPlugin({
fileName: '../manifest.json',
publicPath: 'righttoworkcheck/js/',
}),
],
};This generates static/righttoworkcheck/manifest.json:
{
"custom/candidate/wizard/multiple-upload.js": "righttoworkcheck/js/custom/candidate/wizard/multiple-upload.dd215078.js"
}All configuration is optional. Add to your settings.py:
DJANGO_MULTI_MANIFEST_LOADER = {
# Enable/disable manifest caching
# Default: True in production (not DEBUG), False in DEBUG mode
'cache': True,
# Name for main app manifests (manifests not tied to a specific app)
# Default: 'main'
'main_app_name': 'dashboard',
# Enable debug logging to see which manifests are loaded
# Default: False
'debug': False,
}| Option | Type | Default | Description |
|---|---|---|---|
cache |
bool |
not DEBUG |
Cache merged manifests in memory. Disable in development for hot reloading. |
main_app_name |
str |
'main' |
Name used for main/shared manifests that aren't tied to a specific app. |
debug |
bool |
False |
Enable detailed logging of manifest loading process. |
# Clone the repository
cd django-multi-manifest-loader
# Create virtual environment
python3 -m venv .venv
# Activate virtual environment
source .venv/bin/activate # Linux/Mac
# or
.venv\Scripts\activate # Windows
# Install package with dev dependencies
pip install -e ".[dev]"# Run all tests
pytest
# Run with coverage report
pytest --cov=django_multi_manifest_loader --cov-report=term-missing
# Run specific test file
pytest tests/test_manifest_loader.py -v# Run all quality checks
flake8 django_multi_manifest_loader/ tests/
black --check django_multi_manifest_loader/ tests/
isort --check-only django_multi_manifest_loader/ tests/
ruff check django_multi_manifest_loader/ tests/
# Auto-format code
black django_multi_manifest_loader/ tests/
isort django_multi_manifest_loader/ tests/
ruff check --fix django_multi_manifest_loader/ tests/During development, you can clear the manifest cache:
from django_multi_manifest_loader import ManifestLoader
ManifestLoader.clear_cache()Enable debug mode to see detailed logging:
# settings.py
DJANGO_MULTI_MANIFEST_LOADER = {
'debug': True,
}This will log:
- Number of manifest files found
- Path to each manifest being loaded
- Number of entries in each manifest
- Total merged entries
By default, caching is disabled in DEBUG mode (cache=not DEBUG). This means:
- Production: Manifests are cached for performance
- Development: Manifests are reloaded on each request for hot reloading
- Python >= 3.10
- Django >= 4.0
No other dependencies! This package is completely standalone.
The loader uses Django's staticfiles finders to discover manifest files:
- Main manifest: Searches for
manifest.jsonin static directories - Package manifests: Iterates through all
INSTALLED_APPSand checks each for<app_name>/manifest.json - Merging: All found manifests are merged into a single dictionary (later entries override earlier ones)
- Caching: Merged manifest is cached in memory (unless disabled)
The package automatically publishes to PyPI when you push a version tag:
# Update version in __init__.py
# Commit changes
git add django_multi_manifest_loader/__init__.py
git commit -m "Bump version to 0.2.0"
# Create and push tag
git tag v0.2.0
git push origin main --tagsThis triggers the GitHub Actions workflow which:
- Runs all tests across Python 3.10-3.12 and Django 4.2-5.1
- Runs all linters (flake8, black, isort, ruff)
- Builds the package
- Publishes to PyPI using trusted publishing (no API tokens needed)
# Build package
python -m build
# Check package
twine check dist/*
# Upload to PyPI (requires PyPI credentials)
twine upload dist/*MIT