Web

Fix Django AJAX in External JS Files: Causes & Fixes

Troubleshoot Django AJAX failing in external JavaScript: learn why template tags, static file loading order, and CSRF tokens break requests and how to fix them.

1 answer 1 view

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:

html
<main data-item-id="{{ product.id }}" data-csrf-token="{{ csrf_token }}">

JavaScript:

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

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%7D or 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:

  1. Put the URL (and any other templated values) on the DOM as data attributes and read them from your external script:

Template:

html
<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:

javascript
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, ... })
  1. Or set a tiny inline variable before loading the external file:

Template:

html
<script>window.ADD_TO_CART_URL = "{% url 'cart:add_to_cart' %}";</script>
<script src="{% static 'js/cart.js' %}"></script>

External JS:

javascript
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.staticfiles is in INSTALLED_APPS:
django
{% 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 defer so the DOM is ready when the script runs.
  • In development, confirm the script file returns 200 in DevTools Network. In production, run collectstatic and 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

  1. Meta tag or data attribute + header per-request:
    Template:
html
<meta name="csrf-token" content="{{ csrf_token }}">

External JS:

javascript
const csrftoken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

$.ajax({
 type: 'POST',
 url: url,
 data: { productId },
 headers: { 'X-CSRFToken': csrftoken }
});
  1. Read from cookie (vanilla getCookie) and set up $.ajax globally. Example getCookie from a community gist: https://gist.github.com/sirodoht/fb7a6e98d33fc460d4f1eadaff486e7b
javascript
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 defined fix 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 inline window. 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-CSRFToken was 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

  1. Minimal and robust — data-attribute + jQuery post:
    Template:
html
<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:

javascript
$(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); }
 });
 });
});
  1. Global inline variable (if you prefer to keep data-* minimal):
    Template:
html
<script>window.ADD_TO_CART_URL = "{% url 'cart:add_to_cart' %}";</script>
<script src="{% static 'js/cart.js' %}"></script>
  1. 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.

  2. If you’re using fetch:

javascript
fetch(url, {
 method: 'POST',
 headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken },
 body: JSON.stringify({ productId })
}).then(r => r.json()).then(data => { /* update UI */ });
  1. If the external file still doesn’t execute, test with a tiny console.log at top of the file to confirm it runs:
javascript
console.log('cart.js loaded');

If you don’t see that message, the file isn’t being fetched or executed.


Sources


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.

Authors
Verified by moderation
Moderation
Fix Django AJAX in External JS Files: Causes & Fixes