Skip to main content

Django ManifestStaticFiles Enhanced

Enhanced ManifestStaticFilesStorage for Django.

AI tools were used for creation of this package

  • Tool(s) used: Claude Code
  • What it was used for: Used for building initial versions of all features and for initial versions of original tests.

Features

This package includes several improvements to Django's ManifestStaticFilesStorage:

  • ticket_27929: keep_original_files option to control whether original files are deleted after hashing
  • ticket_21080: Improved pattern matching, with optional CSS lexer for better URL parsing in CSS files
  • ticket_34322: Improved pattern matching, with optional JS lexer for ES module support in JavaScript files
  • ticket_28200: Optimized storage to avoid unnecessary file operations for unchanged files
  • ticket_26329: Ensure production errors are raised in development too
  • ticket_23517: Collect static files in parallel
  • ticket_36968: Provide better error messages when collectstatic fails
  • new_feature 127: Configurable using the OPTIONS dict of STORAGES setting
  • new_feature 173: Warn instead of error for missing sourcemap files during collectstatic

Compatibility

  • Django: 4.2, 5.0, 5.1, 5.2, 6.0, 6.1
  • Python: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14

Installation

pip install django-manifeststaticfiles-enhanced

Usage

Basic Usage

Replace Django's default ManifestStaticFilesStorage with the enhanced version using the STORAGES setting:

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
    },
}

Add django_manifeststaticfiles_enhanced to your INSTALLED_APPS before contrib.staticfiles

# settings.py
INSTALLED_APPS = [
    ...
    "django_manifeststaticfiles_enhanced",
    "contrib.staticfiles",
    ...
]

Configuration Options

keep_original_files (ticket_27929)

Control whether original files are kept with the hashed file:

# settings.py - Keep original files (default)
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "keep_original_files": True,  # Default
        },
    },
}

# Or discard original files to save space
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "keep_original_files": False,
        },
    },
}

JavaScript Module Support (ticket_34322)

Enable ES module import/export processing:

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "support_js_module_import_aggregation": True,
        },
    },
}

Turn on lexer (ticket_34322)

Use the lexer for slightly improved support but with a small performance cost.

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "support_js_module_import_aggregation": True,
            "use_lexer": True,
        },
    },
}

Raising missing Sourcemap warnings as exceptions

Unlike django, missing sourcmap links in js/css files will not cuase an exception that prevents other processing, but will only produce a warning. This can be changed to an exception to match django by setting sourcemap_strict.

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "sourcemap_strict": True,
        },
    },
}

Easy access to existing options

Disable manifest_strict

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "manifest_strict": False,
        },
    },
}

Also available:

  • manifest_name: change the name of the staticfiles.json file

Skip files already hashed by a bundler (prehashed)

JS bundlers (Vite, webpack, rollup, esbuild, etc) add their own cache busting filenames, e.g. dist/assets/app.4889e940.js. When those land in your static dir, collectstatic would hash them again (app.4889e940.a1b2c3d4.js). Worse still, with keep_original_files=False it can delete the chunked files the bundler actually references in production.

This option allows you to skip processing of those files and add them to the manifest as their own name. It's up to you to provide a callable (or a "dotted.path.to_callable") that receives a file name and returns True when that file already has a cache busting filename:

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "prehashed": "myapp.staticfiles.is_vite_output",
        },
    },
}

A callable might just match a naming convention:

# myapp/staticfiles.py
import re

_BUNDLER_HASH = re.compile(r"\.[0-9a-f]{8,}\.[a-z0-9]+$")

def is_vite_output(name):
    return name.startswith("dist/") and bool(_BUNDLER_HASH.search(name))

For a definitive answer, you could read the bundler's own output manifest. The callable below reads Vite's manifest.json to get a definitive answer. It uses the cached set as the callable will be used for each static file.

# myapp/staticfiles.py
import json
from functools import lru_cache

@lru_cache(maxsize=1)
def _vite_outputs():
    # Vite's manifest lists every emitted file under "file"/"css"/"assets".
    with open("frontend/dist/.vite/manifest.json") as f:
        manifest = json.load(f)
    outputs = set()
    for entry in manifest.values():
        if "file" in entry:
            outputs.add("dist/" + entry["file"])
        outputs.update("dist/" + p for p in entry.get("css", []))
        outputs.update("dist/" + p for p in entry.get("assets", []))
    return outputs

def is_vite_output(name):
    return name.replace("\\", "/") in _vite_outputs()

--parallel command line option

By default the copying of files to the static folder uses 10 threads, this should be optimal for most projects but you can supply the --parallel option to add more workers if you have lots of files and fast io, or less workers if you have few files and slow io (NAS). Set --parallel to 1 to disable parallel collection.

Feature Details

CSS Processing Improvements (ticket_21080)

Improved CSS URL processing, providing:

  • Ignores url's in comments
  • More reliable URL extraction
  • Lexer option for slightly wider @import support ('@import /* comment */ "file.css"')

File Operation Optimization (ticket_28200)

Reduces unnecessary file operations during collectstatic:

  • Avoids recreating files that haven't changed
  • Checks file existence before deletion

JavaScript Module Support (ticket_34322)

Improved JS import/export processing, providing:

  • Covers ES6 import/export statements and dynamic imports
  • Ignores statements in comments and strings
  • Supports with attribute in imports, import sheet from './styles.css' with { type: 'css' };
  • Lexer option for more complex cases (import(module.js?t=${Date.now()}))

Example JavaScript that gets processed:

// Before processing
import { Component } from './component.js';
export { utils } from './utils.js';

// After processing (with hashing)
import { Component } from './component.abc123.js';
export { utils } from './utils.def456.js';

Option to not move the original asset to your static folder (ticket_27929)

Control file cleanup behavior:

# Keep original files (default)
keep_original_files = True
# Results in: style.css + style.abc123.css

# Delete original files
keep_original_files = False  
# Results in: style.abc123.css only

Ignoring specific errors

Ignore specific errors during post-processing with the ignore_errors option. This is useful when you have third-party libraries that reference non-existent files or use dynamic path construction that can't be properly parsed.

# settings.py
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.EnhancedManifestStaticFilesStorage",
        "OPTIONS": {
            "ignore_errors": [
                # Format: "file_pattern:missing_url_pattern"
                "vendor/bootstrap/*.css:missing-font.woff",  # Ignore missing font in bootstrap CSS
                "vendor/es/*.js:*",  # Ignore all missing missing references in vendors ES version
                "*/*.css:../img/background.png"  # Ignore specific missing image in all CSS files
            ],
        },
    },
}

Patterns support wildcard matching with * to match any number of characters.

Catching common errors between development and production

The {% static %} tag does not use the manifest file in development, when DEBUG=True, instead it defaults to StaticFilesStorage. This can lead to errors that only show up in staging/production enviornments, because some static paths will work for StaticFilesStorage but not be valid paths in the manifest file and cause errors when DEBUG=False. This class adds extra validation when DEBUG=True so that the errors can be caught earlier. This package also includes a special storage class, TestingManifestStaticFilesStorage, for testing that uses StaticFilesStorage with the validation checks.

# settings.py for testing
STORAGES = {
    "staticfiles": {
        "BACKEND": "django_manifeststaticfiles_enhanced.storage.TestingManifestStaticFilesStorage",
    },
}

The TestingManifestStaticFilesStorage includes the DebugValidationMixin which performs the following validations:

  1. Rejects paths that start with / (e.g., /static/file.css)
  2. Rejects paths that use backslashes (e.g., path\to\file.css)
  3. Checks if the file actually exists in your static files
  4. Detects case sensitivity issues that might work in development but fail in production

Collect static files in parallel

  • Uses threadpool workers to speed up copying files from finders

Running Tests

# Install development dependencies
pip install -e .[dev]

# Run tests
python tests/runtests.py

# Or run specific test modules
python tests/runtests.py staticfiles_tests.test_storage

Migration from Django's ManifestStaticFilesStorage

This package is designed as a drop-in replacement:

  1. Install the package
  2. Update your STORAGES setting
  3. Add django_manifeststaticfiles_enhanced to INSTALLED_APPS above contrib.staticfiles
  4. Run python manage.py collectstatic as usual

All existing functionality remains the same, with additional features available through configuration options.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the BSD 3-Clause License - the same license as Django.

Changelog

0.10.0

  • Option to ignore processing of JS bundlers like Vite, webpack, etc.

0.9.0

  • Only warn on missing sourcemaps, with sourcemap_strict option to restore old behaviour of ending all processing with an error.

0.8.0

  • Added improved regex approach and made the lexer opt-in via use_lexer param

0.7.0

  • Add collectstatic command with parallelization, which can be customized with --parallel option

0.6.0

  • Surface exceptions with {% static %} tag when DEBUG=True
  • Added TestingManifestStaticFilesStorage for use in tests to Surface exceptions when DEBUG=False

0.5.0

  • Made support of js import/export statements opt in

0.4.0

  • Added handling of circular dependencies in js/css files.

0.3.0

  • Added ignore_errors option to allow ignoring specific file reference errors during post-processing
  • Improved error handling to continue processing when errors are explicitly ignored

0.2.0

  • Performance improvements
  • Improved exception messages
  • Fixed issue with js dynamic imports that include template literals
  • Improved handling of soucerMapURLs

0.1.0 (Initial Release)

  • Includes keep_original_files option (ticket_27929)
  • Includes CSS lexer improvements (ticket_21080)
  • Includes file operation optimizations (ticket_28200)
  • Includes JavaScript module support (ticket_34322)
  • Added comprehensive test suite
  • Support for Django 4.2+

Metadata

Release files for django-manifeststaticfiles-enhanced 0.10.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-manifeststaticfiles-enhanced 0.10.1
File Size Uploaded
django_manifeststaticfiles_enhanced-0.10.1.tar.gz 33.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-manifeststaticfiles-enhanced 0.10.1
File Interpreter ABI Platform
django_manifeststaticfiles_enhanced-0.10.1-py3-none-any.whl Python 3 none any Details

Total release size: 65.5 kB

Release files / django_manifeststaticfiles_enhanced-0.10.1.tar.gz

Download URL django_manifeststaticfiles_enhanced-0.10.1.tar.gz
Size 33.9 kB
Tags Source
SHA-256 checksum
How to use checksums
3a87247798089078ff772511dd886f65bb9499cb1bd936bbbb8dc4c185567913
BLAKE2b-256 checksum
How to use checksums
ecd1cc510e62ae10f879ede66cbfca8584381245de0c927ec29122caa5fd528b
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 9, 2026.

Transparency log

Release files / django_manifeststaticfiles_enhanced-0.10.1-py3-none-any.whl

Download URL django_manifeststaticfiles_enhanced-0.10.1-py3-none-any.whl
Size 31.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e58f9a0ca82e9aefd91e10504ae750732d4f1f0a35c03857fb588be7be11fa22
BLAKE2b-256 checksum
How to use checksums
9bb8a7077a537cc3a8087464de1998eb1d65485f8b3062b483ca23b3895ad517
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 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.1 This release

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

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