Render Django template comments as HTML comments in DEBUG mode.
This Django app converts Django template comments into HTML comments when DEBUG=True,
making them visible in the page source and browser developer tools.
Normally, Django template comments ({# comment #} and
{% comment %}...{% endcomment %}) are stripped out during template processing, so they do not
appear in the rendered HTML output. With this app installed, these comments
are transformed into HTML comments (<!-- comment -->) instead when DEBUG=True, allowing developers to see them
directly in the rendered HTML. This is useful for debugging, documentation, and
collaboration. When DEBUG=False, comments are stripped and everything inside them is ignored (not parsed) as usual by Django's template engine.
An alternative would be to use HTML comments directly in templates, as these are not stripped by Django, but that approach has some downsides:
- They are included in the rendered output in production (i.e. when
DEBUG=False), increasing the HTML payload size and potentially exposing internal notes, debugging information, or other sensitive information to end users. - HTML comments are not ignored by the Django template engine so
any content inside them (e.g.
<!-- {% if user.is_authenticated %}...{% endif %} -->) will still be processed by the Django template engine, potentially causing errors or unintended behavior (e.g. see this Stack Overflow post).
By using this app, developers can use Django's comment tags for all their comments, and have them visible during development and automatically stripped out in production.
Any comments that should always
be hidden (even when DEBUG=True) can be marked with a special !hide marker to ensure they are never rendered
in the output HTML.
By default, the content from comments will be shown verbatim in the rendered HTML comment and any tags or filters it includes will be ignored by Django's template engine during processing,
matching Django's standard behavior. However, adding a !render marker to the comment overrides this so that template tags and filters in the content are processed.
pip install django-render-commentsOr with uv:
uv add django-render-commentsIn your Django settings.py:
- Add to INSTALLED_APPS (optional, for app registry):
INSTALLED_APPS = [
# ...
'django_render_comments',
]- Configure template loaders:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'OPTIONS': {
'loaders': [
'django_render_comments.loaders.filesystem.Loader',
'django_render_comments.loaders.app_directories.Loader',
],
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
# ... other context processors
],
},
},
]Note: When specifying custom loaders, APP_DIRS must not be set (or be False).
Template:
<div>
{# Debug: user={{ user }} #}
{% comment %}Old navigation{% endcomment %}
<nav>...</nav>
</div>Output (comments stripped):
<div>
<nav>...</nav>
</div>Output (comments visible in HTML, template tags appear literally):
<div>
<!-- Debug: user={{ user }} -->
<!-- Old navigation -->
<nav>...</nav>
</div>Note: Template tags like {{ user }} appear literally in the comment output - they are not processed by Django's template engine. This matches the behavior of Django's native {% comment %} block, which protects its content from being processed.
| Setting | Type | Default | Description |
|---|---|---|---|
DEBUG |
bool | False | Comments only converted when True |
RENDER_COMMENTS_ENABLED |
bool | True | Set to False to disable even in DEBUG mode |
# settings.py
RENDER_COMMENTS_ENABLED = False # App will not process comments, even if DEBUG=True.| Django Syntax | Converted To |
|---|---|
{# comment #} |
<!-- comment --> |
{% comment %}...{% endcomment %} |
<!-- ... --> |
{% comment "note" %}...{% endcomment %} |
<!-- [note] ... --> |
Hidden Comments
Sometimes you may want to keep a comment out of HTML output, even during development. Use the
!hide marker for inline comments or "!hide" note for block comments to ensure they are
always stripped.
| Comment Type | Normal (converted to HTML) | Hidden (always stripped) |
|---|---|---|
| Inline | {# comment #} |
{# !hide comment #} |
| Block | {% comment %}...{% endcomment %} |
{% comment "!hide" %}...{% endcomment %} |
| Block with note | {% comment "note" %}...{% endcomment %} |
{% comment "!hide note" %}...{% endcomment %} |
| Condition | Normal comments | Hidden comments |
|---|---|---|
| App installed, DEBUG=True | Converted to HTML comment | Stripped |
| App installed, DEBUG=False | Stripped | Stripped |
| App NOT installed | Stripped | Stripped |
<div>
{# !hide TODO: Fix security issue in auth flow #}
{% comment "!hide" %}
Internal: This endpoint bypasses rate limiting
{% endcomment %}
{% comment "!hide todo - fix auth" %}
Temporary workaround for authentication
{% endcomment %}
<p>Content</p>
</div>With DEBUG=True, renders as:
<div>
<p>Content</p>
</div>The hidden comments are completely removed from the output.
By default, any template tags it contains appear literally in the HTML output and are not processed by Django's template engine. This matches how Django's comments normally behave during template rendering.
{# Debug: user={{ user.email }} #}
{% comment %}{% if debug %}show{% endif %}{% endcomment %}With DEBUG=True, renders as:
<!-- Debug: user={{ user.email }} -->
<!-- {% if debug %}show{% endif %} -->For debugging scenarios where you want tags and filters in a comment's content to be processed, for example to see actual variable values,
simply add a !render marker to the comment as shown below:
| Comment Type | Syntax |
|---|---|
| Inline | {# !render user={{ user.name }} #} |
| Block | {% comment "!render" %}{{ value }}{% endcomment %} |
| Block with note | {% comment "!render note" %}{{ value }}{% endcomment %} |
| Block with dynamic note | {% comment "!render {{ var }}" %}{{ value }}{% endcomment %} |
{# !render Current user: {{ user.username }} #}
{% comment "!render {{ request.method }}" %}
Request ID: {{ request.id }}
{% endcomment %}With DEBUG=True and user.username="john", request.method="GET", request.id="abc123", renders as:
<!-- Current user: john -->
<!-- [GET] Request ID: abc123 -->The !render marker is removed from the output, and template tags are processed (both in the note and in the comment body). Important: Template rendering will only occur when DEBUG=True, so you will usually want to ensure the content does not have side effects outside of the comment (e.g. modifying context variables) that is relied on elsewhere as it will be ignored when DEBUG=False.
If both !hide and !render markers are present in a comment (in any order), !hide always takes precedence. The comment will be hidden and no template processing will occur.
{# !hide !render {{ secret }} #} {# Hidden - !hide wins #}
{# !render !hide {{ secret }} #} {# Also hidden - !hide still wins #}
{% comment "!render !hide" %}...{% endcomment %} {# Hidden #}This ensures that marking a comment as hidden cannot be accidentally bypassed by also adding !render.
-
Comments in JavaScript strings: Comment patterns inside
<script>tags will also be converted. If you have JavaScript containing{# ... #}as string literals, they will be transformed. -
Comment patterns in attributes:
<div data-info="{# test #}">will become<div data-info="<!-- test -->">. -
Nested HTML comments: If your Django comments contain
--, they will be escaped to- -to prevent breaking HTML comment syntax. -
Performance: The preprocessing adds minimal overhead as it uses compiled regex patterns and only runs in DEBUG mode.
- Python 3.10+
- Django 4.2, 5.0, 5.1, 5.2, or 6.0
# Clone the repository
git clone https://github.com/sean-reed/django-render-comments
cd django-render-comments
# Install dependencies
uv sync --group dev
# Run tests (current environment)
uv run pytest
# Run tests with coverage
uv run pytest --cov
# Run full test matrix (Python 3.10-3.13 x Django 4.2-6.0)
uv run nox
# Run specific Python/Django combination
uv run nox -s "tests-3.10(django='4.2')"
# Run linting
uv run nox -s lint
# Run type checking
uv run nox -s typecheck
# List all available test sessions
uv run nox --listMIT License