Introduction
Ajax (Asynchronous JavaScript and XML) lets a Django page send and receive data from the server without a full page reload. The integration involves three parts: a Django view that returns JSON instead of HTML, a URL route pointing to that view, and JavaScript on the frontend that calls the URL and updates the page with the response. Django's built-in JsonResponse and CSRF protection work directly with Ajax — no extra packages needed.
Basic Ajax GET Request
Django View
1# views.py
2from django.http import JsonResponse
3
4def search_users(request):
5 query = request.GET.get('q', '')
6 users = list(
7 User.objects.filter(username__icontains=query)
8 .values('id', 'username', 'email')[:10]
9 )
10 return JsonResponse({'users': users})
URL Configuration
1# urls.py
2from django.urls import path
3from . import views
4
5urlpatterns = [
6 path('api/search-users/', views.search_users, name='search_users'),
7]
JavaScript (Fetch API)
1<!-- template.html -->
2<input type="text" id="search-input" placeholder="Search users...">
3<div id="results"></div>
4
5<script>
6const input = document.getElementById('search-input');
7const results = document.getElementById('results');
8
9input.addEventListener('input', async function() {
10 const response = await fetch(`/api/search-users/?q=${encodeURIComponent(this.value)}`);
11 const data = await response.json();
12
13 results.innerHTML = data.users
14 .map(u => `<div>${u.username} (${u.email})</div>`)
15 .join('');
16});
17</script>
Ajax POST with CSRF Token
Django requires a CSRF token for POST requests. Include it in the Ajax header:
Django View
1# views.py
2from django.http import JsonResponse
3from django.views.decorators.http import require_POST
4import json
5
6@require_POST
7def add_comment(request):
8 data = json.loads(request.body)
9 comment = Comment.objects.create(
10 post_id=data['post_id'],
11 text=data['text'],
12 author=request.user,
13 )
14 return JsonResponse({
15 'id': comment.id,
16 'text': comment.text,
17 'author': comment.author.username,
18 'created': comment.created_at.isoformat(),
19 })
JavaScript with CSRF
1<script>
2// Get CSRF token from the cookie
3function getCookie(name) {
4 let value = `; ${document.cookie}`;
5 let parts = value.split(`; ${name}=`);
6 if (parts.length === 2) return parts.pop().split(';').shift();
7}
8
9async function addComment(postId, text) {
10 const response = await fetch('/api/add-comment/', {
11 method: 'POST',
12 headers: {
13 'Content-Type': 'application/json',
14 'X-CSRFToken': getCookie('csrftoken'),
15 },
16 body: JSON.stringify({ post_id: postId, text: text }),
17 });
18 const data = await response.json();
19 return data;
20}
21</script>
The {% csrf_token %} template tag must appear somewhere on the page (usually in a form) for the cookie to be set, or use the @ensure_csrf_cookie decorator on the page view.
Using jQuery (Alternative)
1<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
2<script>
3// jQuery sets the CSRF header automatically with this setup
4$.ajaxSetup({
5 beforeSend: function(xhr, settings) {
6 if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
7 xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
8 }
9 }
10});
11
12// GET request
13$.get('/api/search-users/', { q: 'alice' }, function(data) {
14 console.log(data.users);
15});
16
17// POST request
18$.ajax({
19 url: '/api/add-comment/',
20 type: 'POST',
21 contentType: 'application/json',
22 data: JSON.stringify({ post_id: 1, text: 'Great post!' }),
23 success: function(data) {
24 console.log('Comment added:', data);
25 },
26 error: function(xhr) {
27 console.error('Error:', xhr.responseJSON);
28 }
29});
30</script>
Submit a Django form without page reload:
1# views.py
2from django.http import JsonResponse
3
4def contact_form(request):
5 if request.method == 'POST':
6 form = ContactForm(request.POST)
7 if form.is_valid():
8 form.save()
9 return JsonResponse({'success': True})
10 return JsonResponse({'success': False, 'errors': form.errors}, status=400)
11 return render(request, 'contact.html', {'form': ContactForm()})
1<!-- contact.html -->
2<form id="contact-form">
3 {% csrf_token %}
4 {{ form.as_p }}
5 <button type="submit">Send</button>
6</form>
7<div id="message"></div>
8
9<script>
10document.getElementById('contact-form').addEventListener('submit', async function(e) {
11 e.preventDefault();
12 const formData = new FormData(this);
13
14 const response = await fetch(this.action || window.location.href, {
15 method: 'POST',
16 headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') },
17 body: formData,
18 });
19 const data = await response.json();
20
21 if (data.success) {
22 document.getElementById('message').textContent = 'Sent!';
23 this.reset();
24 } else {
25 document.getElementById('message').textContent = JSON.stringify(data.errors);
26 }
27});
28</script>
Detecting Ajax Requests in Django
1def my_view(request):
2 # Check if the request is Ajax
3 if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
4 # Return JSON for Ajax
5 return JsonResponse({'data': 'value'})
6
7 # Return HTML for normal requests
8 return render(request, 'page.html')
Note: The Fetch API does not set X-Requested-With by default. Either add it manually or check Accept: application/json instead:
1def my_view(request):
2 if 'application/json' in request.headers.get('Accept', ''):
3 return JsonResponse({'data': 'value'})
4 return render(request, 'page.html')
Error Handling
1# views.py
2from django.http import JsonResponse
3import json
4
5def api_view(request):
6 try:
7 data = json.loads(request.body)
8 result = process(data)
9 return JsonResponse({'result': result})
10 except json.JSONDecodeError:
11 return JsonResponse({'error': 'Invalid JSON'}, status=400)
12 except Exception as e:
13 return JsonResponse({'error': str(e)}, status=500)
1// Frontend error handling
2async function apiCall(url, data) {
3 try {
4 const response = await fetch(url, {
5 method: 'POST',
6 headers: {
7 'Content-Type': 'application/json',
8 'X-CSRFToken': getCookie('csrftoken'),
9 },
10 body: JSON.stringify(data),
11 });
12
13 if (!response.ok) {
14 const error = await response.json();
15 throw new Error(error.error || `HTTP ${response.status}`);
16 }
17
18 return await response.json();
19 } catch (err) {
20 console.error('API error:', err.message);
21 throw err;
22 }
23}
Common Pitfalls
403 Forbidden (CSRF): POST requests without the X-CSRFToken header are rejected. Always include the CSRF token from the cookie or the {% csrf_token %} hidden input.
request.POST is empty for JSON: Django only populates request.POST for form-encoded data. For Content-Type: application/json, use json.loads(request.body) instead.
@csrf_exempt temptation: Disabling CSRF for Ajax views creates a security vulnerability. Use the CSRF token header instead.
Missing Content-Type header: If you send JSON without Content-Type: application/json, Django may try to parse it as form data. Always set the content type header explicitly.
Serializing querysets: JsonResponse(queryset) fails. Convert querysets to lists first: list(qs.values('id', 'name')) or use Django REST Framework serializers.
Summary
Return JsonResponse from Django views for Ajax endpoints
Include the CSRF token via X-CSRFToken header for POST/PUT/DELETE requests
Use json.loads(request.body) to parse JSON POST data (not request.POST)
Use the native Fetch API or jQuery's $.ajax() on the frontend
Handle errors on both sides — return proper HTTP status codes from Django and catch them in JavaScript
For complex APIs, consider Django REST Framework for serialization, authentication, and routing