Why does my Django AJAX code work when the JavaScript is inline in the template but not when it’s moved to an external .js file? I can successfully read the product ID and CSRF token from data attributes and jQuery is loaded, but the AJAX request doesn’t run when the code is in an external file. Example HTML and JavaScript:
HTML:
<main data-item-id="{{ product.id }}" data-csrf-token="{{ csrf_token }}">
JavaScript:
$(document).ready(function(){
var btn = document.querySelector(".add-to-cart");
var productId = document.querySelector('main').getAttribute('data-item-id');
var csrfToken = document.querySelector('main').getAttribute('data-csrf-token');
btn.addEventListener("click", function(){
$.ajax({
type: 'POST',
url: '{% url "cart:add_to_cart"%}',
data: {'productId': productId},
headers: {'X-CSRFToken': csrfToken},
success: function(data){
$("#total-items").text(data.total_items);
}
})
})
})
I also have the script tag and jQuery included. What could be causing the external JS file not to execute the AJAX request, and how can I fix it (considering Django template tags, CSRF, and serving static JS)?
Your AJAX works when the JavaScript is inline because Django processes template tags and template variables in the HTML; external .js files are served as static assets and aren’t run through the Django template engine, so things like {% url "cart:add_to_cart" %} won’t be expanded. Other frequent causes are wrong script load order (jQuery loaded after your file), the static file not actually being served, or a missing/incorrect CSRF header — fix these by exposing templated values in the DOM (data-attributes or a tiny inline var), ensuring the correct static include order, and reading/setting the CSRF token from a meta tag or cookie before sending AJAX requests.
Contents
- Django AJAX and Template Tags
- Django Static Files: correct loading & order
- Django CSRF in external JavaScript
- Common runtime errors and debugging checklist
- Quick fixes and recommended code samples
- Sources
- Conclusion
Django AJAX and Template Tags
Why does inline work? Simple: when your JavaScript lives inside a Django template the template engine replaces template tags and variables (for example {% url ... %} and {{ csrf_token }}) with real strings before the browser sees the page. An external static file is not processed by Django’s template engine, so any template tag inside it remains raw text and will not provide the correct URL or token.
Symptoms of this exact problem:
- In the browser network tab the AJAX request goes to an odd URL like
/%7B%25%20url%20%22cart:add_to_cart%22%20%25%7Dor you see no outbound request at all. - There are no AJAX responses or the server returns 404/400 because the endpoint is wrong.
Two practical patterns to avoid putting template tags in external .js:
- Put the URL (and any other templated values) on the DOM as data attributes and read them from your external script:
Template:
<button class="add-to-cart"
data-url="{% url 'cart:add_to_cart' %}"
data-item-id="{{ product.id }}"
data-csrf-token="{{ csrf_token }}">
Add
</button>
External JS:
const btn = document.querySelector('.add-to-cart');
const url = btn.dataset.url; // reads the rendered URL
const productId = btn.dataset.itemId;
const csrfToken = btn.dataset.csrfToken;
// then call $.ajax({ url, ... })
- Or set a tiny inline variable before loading the external file:
Template:
<script>window.ADD_TO_CART_URL = "{% url 'cart:add_to_cart' %}";</script>
<script src="{% static 'js/cart.js' %}"></script>
External JS:
const url = window.ADD_TO_CART_URL;
Both approaches remove template tags from static files and keep your JS static and cacheable. See common community discussion about passing the CSRF token or URLs from templates to JS: https://stackoverflow.com/questions/23349883/how-to-pass-csrf-token-to-javascript-file-in-django and https://stackoverflow.com/questions/22688032/django-ajax-request-from-external-javascript-file.
Django Static Files: correct loading & order
If your external file never runs the most common causes are: wrong static path, the browser not loading the file, or wrong script order (jQuery loaded after your code). Quick checks and fixes:
- Use Django’s static tag and make sure
django.contrib.staticfilesis in INSTALLED_APPS:
{% load static %}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="{% static 'js/cart.js' %}"></script>
- Put static script includes at the end of the body or use
deferso the DOM is ready when the script runs. - In development, confirm the script file returns 200 in DevTools Network. In production, run
collectstaticand configure your webserver to serve static files; see Django docs on static files: https://docs.djangoproject.com/en/4.1/howto/static-files/. - Clear browser cache or use hashed filenames (ManifestStaticFilesStorage) to avoid stale cached JS.
If jQuery is not defined when your external code runs you’ll see console errors like Uncaught ReferenceError: $ is not defined. Fix the include order (jQuery first) or use an AMD/bundler/defer approach.
Django CSRF in external JavaScript
You already read the CSRF token from a data attribute — that’s valid. But common pitfalls:
- Putting
{% csrf_token %}inside a script tag incorrectly can produce a JS syntax error (don’t output raw token text without quoting). - If you try to read the CSRF token from cookie but you enabled
CSRF_COOKIE_HTTPONLY = True, JavaScript won’t be able to read it. - External JS must set the X-CSRFToken header (or include the token in POST data) for non-safe requests.
Recommended safe patterns
- Meta tag or data attribute + header per-request:
Template:
<meta name="csrf-token" content="{{ csrf_token }}">
External JS:
const csrftoken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
$.ajax({
type: 'POST',
url: url,
data: { productId },
headers: { 'X-CSRFToken': csrftoken }
});
- Read from cookie (vanilla getCookie) and set up $.ajax globally. Example getCookie from a community gist: https://gist.github.com/sirodoht/fb7a6e98d33fc460d4f1eadaff486e7b
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Django documents this header technique here: https://docs.djangoproject.com/en/5.2/howto/csrf/.
Common runtime errors and debugging checklist
When your inline version works but external doesn’t, follow this checklist — they solve 90% of cases:
- Open DevTools → Console: any errors?
Uncaught ReferenceError: $ is not defined,TypeError: Cannot read property 'addEventListener' of null, or a SyntaxError will stop execution. - Network tab: is the external .js fetched (200)? Is the AJAX request being sent at all? If yes, what is the request URL and status code (403 = CSRF failed, 404 = wrong URL)?
- Confirm script order: jQuery first, then your script. If you see
$ is not definedfix order. - Confirm your template tags are not inside the external .js. If you see literal
{% url ... %}in the JS or in the request URL, move the tag into the HTML (data- attribute or inlinewindow.var). - Check that your selector finds the element:
document.querySelector('.add-to-cart')can be null if the element isn’t present. Use event delegation as needed:$(document).on('click', '.add-to-cart', handler). - Check CSRF: if server returns 403, inspect request headers to see if
X-CSRFTokenwas sent; ensure you’re reading the correct token source (cookie vs meta vs data attribute). - Spelling options: jQuery uses
dataType(capital T). A wrong option name can be ignored; it won’t usually break the request but is worth checking. - If you use HTMX or replace DOM segments dynamically, scripts may need re-initialization — prefer event delegation.
If you’re unsure, paste the external JS URL into the browser to inspect the delivered file. That quickly shows whether a template tag remains unrendered or whether the file is stale/cached.
Quick fixes and recommended code samples
- Minimal and robust — data-attribute + jQuery post:
Template:
<button class="add-to-cart"
data-url="{% url 'cart:add_to_cart' %}"
data-item-id="{{ product.id }}"
data-csrf="{{ csrf_token }}">
Add
</button>
External JS:
$(function(){
$(document).on('click', '.add-to-cart', function(){
const btn = this;
const url = btn.dataset.url;
const productId = btn.dataset.itemId;
const csrftoken = btn.dataset.csrf; // or from meta/cookie
$.ajax({
type: 'POST',
url: url,
data: { productId },
headers: { 'X-CSRFToken': csrftoken },
success(data) { $('#total-items').text(data.total_items); }
});
});
});
- Global inline variable (if you prefer to keep data-* minimal):
Template:
<script>window.ADD_TO_CART_URL = "{% url 'cart:add_to_cart' %}";</script>
<script src="{% static 'js/cart.js' %}"></script>
-
Use cookie + $.ajaxSetup so you don’t set headers every request (recommended if you have many AJAX calls). See code in the CSRF section and the gist: https://gist.github.com/sirodoht/fb7a6e98d33fc460d4f1eadaff486e7b.
-
If you’re using fetch:
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken },
body: JSON.stringify({ productId })
}).then(r => r.json()).then(data => { /* update UI */ });
- If the external file still doesn’t execute, test with a tiny console.log at top of the file to confirm it runs:
console.log('cart.js loaded');
If you don’t see that message, the file isn’t being fetched or executed.
Sources
- Django ajax request from external javascript file - Stack Overflow
- How to pass csrf_token to javascript file in django? - Stack Overflow
- AJAX post in external JS file with Django - Stack Overflow
- How to Work With AJAX Request With Django — Simple is Better Than Complex
- Get CSRF token from Django HTML templates with vanilla JS · GitHub Gist
- How to use Django’s CSRF protection
- How to manage static files (e.g. images, JavaScript, CSS) | Django documentation
Conclusion
The most likely cause is that the external file is static (not templated) so your {% url %} or other template code isn’t being expanded — or the script is loaded in the wrong order / not loaded at all, or the CSRF header isn’t being set. For reliable Django AJAX: expose templated values via data-attributes or a tiny inline window variable, ensure static files are served and jQuery loads first, and set X-CSRFToken from a meta tag or cookie (or use $.ajaxSetup). Once you verify the script is fetched, check the console and network tab and you’ll find the specific failure quickly.