In Django templates, how should you pass review data from the view to JavaScript, and why is json_script safer than escapejs?
answer
- HTML escaping is the wrong context
- a quoted string literal only
- data, not code, in the page
- three characters become unicode escapes
- read it back with JSON.parse
basics
~20 sUse {{ data|json_script:'review-data' }}, which serializes to JSON, escapes <, > and & and wraps it in a non-executing application/json script tag that JavaScript reads with JSON.parse; escapejs only makes a value safe inside a quoted JavaScript string literal.
solid answer
~40 sInside `<script>`, normal autoescaping is the wrong encoding: HTML entities are not decoded in script, and `json.dumps(...)` marked `|safe` lets a value containing `</script>` close the tag and inject markup. Django offers two tools. `escapejs` converts quotes, backslashes, angle brackets and similar characters to `\uXXXX` escapes, making a value safe **only** as a whole string inside single or double quotes — not in backtick template literals, not as a number or object. `json_script` serializes a whole Python structure with `DjangoJSONEncoder`, replaces `<`, `>` and `&` with unicode escapes, and emits `<script id="review-data" type="application/json">`, which the browser does not execute. Your script reads it with `JSON.parse(document.getElementById('review-data').textContent)`. It handles lists, dicts, dates and decimals, keeps data separate from code, and works with a CSP that forbids inline script.
code
python · 12 linesfrom django.shortcuts import get_object_or_404, render
from .models import Product
def product_detail(request, pk):
product = get_object_or_404(Product, pk=pk)
payload = [
{'title': r.title, 'rating': r.rating, 'posted': r.created_at}
for r in product.reviews.order_by('-created_at')[:20]
]
return render(request, 'shop/product_detail.html', {'product': product, 'review_payload': payload})go deeper
Recall that json_script puts data in a script tag of type application/json and that you read it with JSON.parse on its textContent.
Explain which characters json_script escapes and why, and why escapejs is only for a value inside a quoted JS string literal.
Spot the json.dumps plus safe pattern in review and move data into json_script or data- attributes so templates contain no inline script.
Adopt a data-islands policy — no values interpolated into inline script — that keeps templates compatible with a strict Content Security Policy.
## The problem: script is not HTML A product page wants to hand review data to a JavaScript widget — say the ratings for a chart and the latest review titles. The obvious templates are all wrong: ```django <script> const title = "{{ review.title }}"; {# HTML entities inside JS #} const data = {{ reviews_json|safe }}; {# </script> breaks out #} </script> ``` - **Plain `{{ }}`** applies HTML escaping. Inside `<script>` the browser does not decode entities, so `"` arrives as the literal text `"`, and HTML escaping was never designed to be the correct encoding for JavaScript. - **`json.dumps()` plus `|safe`** is worse: a review title containing `</script><img src=x onerror=...>` ends the script element early, and the rest is parsed as HTML. JSON encoding alone does not escape `<` or `/`. Django provides two dedicated tools, and they are not equivalent. ## escapejs: one value inside a quoted string literal ```django <script> const title = "{{ review.title|escapejs }}"; </script> ``` `escapejs` replaces backslash, both quote characters, `<`, `>`, `&`, `=`, `-`, `;`, the backtick, the line and paragraph separators and control characters with `\uXXXX` escapes. Its documented contract is narrow: - safe only for a **whole string literal within single or double quotes**; - **not** safe in a backtick **template literal**; - any other use — a bare number, an object, an attribute — is unsupported. It also produces only strings: a list of reviews would have to be assembled by hand. ## json_script: structured data as inert JSON ```django {{ review_payload|json_script:"review-data" }} <script src="/static/js/reviews.js"></script> ``` The `json_script` filter (also available as `django.utils.html.json_script`): 1. serializes the value with **`DjangoJSONEncoder`**, which handles datetimes, dates, times, timedeltas, `Decimal` and `UUID` values; 2. replaces `<`, `>` and `&` with `\u003C`, `\u003E` and `\u0026`, so no string in the data can close the tag; 3. wraps the result in `<script id="review-data" type="application/json">`. The id argument is optional. A `type="application/json"` script block is not executed; it is a data island. Your JavaScript reads it: ```javascript const reviews = JSON.parse(document.getElementById('review-data').textContent); ``` ## Side by side | | `escapejs` | `json_script` | |---|---|---| | Input | one value, as a string | any JSON-serializable structure | | Safe position | inside a quoted JS string literal | anywhere in element content | | Template literals | not safe | not applicable | | Output | escaped text | an inert JSON script element | | Inline script needed | yes | no | | Strict CSP | needs a nonce or hash for the inline script | compatible, per Django's docs | ## Guidance - Prefer **`json_script`** for anything more than a single string; keep your JavaScript in static files. - For one or two scalar values, an HTML **`data-` attribute** with normal autoescaping (`data-review-id="{{ review.pk }}"`) is also recommended by Django's docs over embedding values in script. - Use **`escapejs`** only when you must write a value into an existing inline script, and only inside quotes. - Never mark `json.dumps()` output safe inside `<script>`. - `json_script` output is meant for element content; its own docstring says it is not safe inside a tag attribute.
- Why is {{ reviews_json|safe }} inside a script tag exploitable even though the value is valid JSON?Standard JSON encoding leaves `<` and `/` alone, so a review title containing `</script>` ends the script element and the HTML parser treats what follows as markup, such as an injected image with an onerror handler. `json_script` prevents this by encoding `<`, `>` and `&` as unicode escapes.
- When is escapejs still the right choice?When a single string must be written into an existing inline script inside single or double quotes, such as `const title = '{{ review.title|escapejs }}';`. It is not safe in backtick template literals, and for structured data or strict CSP setups `json_script` or a `data-` attribute is the better option.
saying these in an interview costs you the question
- Normal autoescaping makes values safe inside a script tag.
- json.dumps output marked |safe is fine inside script because it is JSON.
- escapejs makes a value safe anywhere in JavaScript, including template literals.
- json_script output runs as JavaScript when the page loads.
- json_script output is also safe inside an HTML attribute.