How to Add a Calendar to a Django Template (No jQuery, No Admin Hacks)
By SimpleCalendarJS Team
You're building a Django app and you need a calendar in one of your templates. You search "Django calendar" and the results fall into three buckets: Django's admin date widget (a date picker, not a calendar), Python's HTMLCalendar (a static table with no interactivity), and django-scheduler (a full application with models, migrations, and FullCalendar bundled via CDN). None of these give you what you actually want — a lightweight, interactive event calendar you can drop into any Django template. Here's how to get there.
The Django calendar landscape
Django is a batteries-included framework, but the battery it ships for calendars is a small one. The ecosystem breaks down into three tiers:
Django's built-in calendar widget (AdminDateWidget) lets users pick a date in a form field. It lives in django.contrib.admin.widgets, depends on Django's bundled jQuery, and requires {{ form.media }} to inject the admin CSS and JS. It's a date picker for forms — it cannot display events, show week or day views, or handle click interactions on a grid.
Python-side calendar rendering (calendar.HTMLCalendar) generates a static HTML table from Python's standard library. Some developers extend it with Django template tags to render month grids server-side. This works for displaying a read-only month view, but there's no interactivity — no event click handlers, no view switching, no async data loading.
Full Django calendar apps like django-scheduler (1,300+ GitHub stars) provide models, views, recurring event support, and a FullCalendar 6 frontend. django-scheduler is the most feature-rich option on Django Packages with multi-calendar support, timezone handling, and iCal export. But it's an entire application — it adds models to your database, requires migrations, and its bundled templates load Bootstrap 5, Bootstrap Icons, and FullCalendar 6 via CDN, totalling over 200 KB of frontend assets before your own code runs.
| Approach | What It Does | Frontend Weight | Interactivity |
|---|---|---|---|
| AdminDateWidget | Date picker for forms | ~30 KB (admin JS/CSS) | Date selection only |
| HTMLCalendar | Static month table | 0 KB (server-rendered) | None |
| django-scheduler | Full calendar app | ~200+ KB (CDN: Bootstrap + FullCalendar) | Full — but tightly coupled |
| Vanilla JS library | Drop-in calendar component | ~14 KB (SimpleCalendarJS) | Full — decoupled from backend |
Most Django developers don't need a calendar application with its own models. They need a calendar component that renders events from their existing models in an interactive month/week/day view.
The FullCalendar-in-Django path
FullCalendar is the most common answer to "add a calendar to Django." At roughly 680,000 weekly npm downloads, it's the dominant JavaScript calendar library. Here's what the integration looks like:
<!-- templates/calendar.html --> {% load static %} <!DOCTYPE html> <html> <head> <link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1/index.global.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1/index.global.min.js"></script> </head> <body> <div id="calendar"></div> <script> document.addEventListener('DOMContentLoaded', function () { var calendar = new FullCalendar.Calendar( document.getElementById('calendar'), { initialView: 'dayGridMonth', events: '/api/events/', } ); calendar.render(); }); </script> </body> </html>
This works. But note the trade-offs:
- ~43 KB gzipped minimum for the core bundle — and that's just
dayGridMonth. Adding week and day views (timeGridWeek,timeGridDay) requires the timegrid plugin, pushing it higher - CDN-loaded by default, which means your production app depends on an external service
- If you self-host the files via Django's
static/directory, you're managing a multi-file JavaScript distribution alongside your Python code - Advanced features like resource scheduling require a premium license starting at $480/developer
The vanilla JS path (recommended)
A vanilla JavaScript calendar library needs nothing from Django except a <div> and a <script> tag. No special template tags, no form widgets, no admin CSS — just standard HTML that Django renders like any other template.
Here's how to add a calendar to a Django template using SimpleCalendarJS — ~14 KB gzipped, zero dependencies:
Step 1: Install and collect static files
npm install simple-calendar-js
Copy the dist files into your Django app's static directory:
mkdir -p myapp/static/myapp/vendor/simple-calendar-js cp node_modules/simple-calendar-js/dist/simple-calendar-js.min.js myapp/static/myapp/vendor/simple-calendar-js/ cp node_modules/simple-calendar-js/dist/simple-calendar-js.min.css myapp/static/myapp/vendor/simple-calendar-js/
Step 2: Create the Django view
# myapp/views.py from django.shortcuts import render def calendar_view(request): return render(request, 'myapp/calendar.html')
# myapp/urls.py from django.urls import path from . import views urlpatterns = [ path('calendar/', views.calendar_view, name='calendar'), ]
Step 3: Build the template
<!-- myapp/templates/myapp/calendar.html --> {% extends "base.html" %} {% load static %} {% block extra_css %} <link rel="stylesheet" href="{% static 'myapp/vendor/simple-calendar-js/simple-calendar-js.min.css' %}"> {% endblock %} {% block content %} <div id="calendar"></div> {% endblock %} {% block extra_js %} <script src="{% static 'myapp/vendor/simple-calendar-js/simple-calendar-js.min.js' %}"></script> <script> document.addEventListener('DOMContentLoaded', function () { var calendar = new SimpleCalendarJs(document.getElementById('calendar'), { defaultView: 'month', locale: 'en-US', enabledViews: ['month', 'week', 'day'], fetchEvents: async function (start, end) { var response = await fetch( '/api/events/?from=' + start.toISOString() + '&to=' + end.toISOString() ); return response.json(); }, onEventClick: function (event) { console.log('Event clicked:', event); }, onSlotClick: function (date) { console.log('Slot clicked:', date); }, }); }); </script> {% endblock %}
That's it. Standard Django template inheritance, standard {% static %} tags, standard JavaScript. No admin imports, no jQuery, no CDN dependency.
What this gives you
- Month, week, and day views with a built-in toolbar for switching
- Async event fetching —
fetchEventsfires with the visible date range on every navigation, so you only load what's on screen - Click handlers —
onEventClickfor existing events,onSlotClickfor empty time slots (wire it to a modal for creating events) - 34+ locales built in — pass
locale: 'pt-BR'orlocale: 'ja-JP'and the calendar renders in that language - No collectstatic surprises — two files in your
static/directory, both cache-friendly
Feeding Django model data to the calendar
The calendar needs events. In Django, you've probably got a model like this:
# myapp/models.py from django.db import models class Event(models.Model): title = models.CharField(max_length=200) start = models.DateTimeField() end = models.DateTimeField() description = models.TextField(blank=True)
Option A: JSON API view (recommended for dynamic calendars)
Create a simple Django view that returns events as JSON. No Django REST Framework required — JsonResponse handles it:
# myapp/views.py from django.http import JsonResponse from .models import Event def event_api(request): start = request.GET.get('from') end = request.GET.get('to') events = Event.objects.filter( start__gte=start, end__lte=end, ).values('id', 'title', 'start', 'end', 'description') return JsonResponse(list(events), safe=False)
# myapp/urls.py urlpatterns = [ path('calendar/', views.calendar_view, name='calendar'), path('api/events/', views.event_api, name='event-api'), ]
The calendar's fetchEvents calls /api/events/ with the visible date range. Django queries only the events in that window and returns JSON. No full-page reload, no template re-rendering.
Option B: Inline data via json_script (for static or pre-loaded calendars)
If your events don't change often, you can pass them directly from the view context using Django's json_script filter:
# myapp/views.py from django.shortcuts import render from .models import Event import json def calendar_view(request): events = list(Event.objects.all().values('id', 'title', 'start', 'end')) return render(request, 'myapp/calendar.html', {'events': events})
<!-- In the template --> {{ events|json_script:"event-data" }} <script> var events = JSON.parse( document.getElementById('event-data').textContent ); var calendar = new SimpleCalendarJs(document.getElementById('calendar'), { defaultView: 'month', events: events, }); </script>
The json_script filter safely serializes Python data into a <script type="application/json"> tag — no XSS risk from user-generated content, no manual JSON escaping.
Theming the calendar to match your Django app
SimpleCalendarJS uses CSS custom properties. Override them in your stylesheet or in a <style> block:
.uc-calendar { --cal-primary: #092e20; --cal-primary-dark: #0c3b2e; --cal-today-bg: #e8f5e9; --cal-font-size: 14px; }
Four lines and your calendar matches Django's signature green. For dark mode:
@media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #1a1a2e; --cal-text: #e0e0e0; --cal-border: #2a2a3e; --cal-today-bg: #2a2a3e; } }
No deeply nested selector overrides, no !important flags, no fighting with admin CSS specificity.
Bundle size: what you're shipping to the browser
Django templates are server-rendered HTML — every kilobyte of JavaScript you add is payload the client downloads and parses. In an ecosystem where developers choose Django specifically because it's server-first and efficient, bloated frontend libraries undercut the architecture.
| Setup | Gzipped Size | Files to Manage | jQuery Required |
|---|---|---|---|
| Django AdminDateWidget | ~30 KB | 0 (built-in) | Yes (bundled) |
| FullCalendar 6 (CDN) | ~43 KB+ | 0 (CDN) | No |
| FullCalendar 6 (self-hosted) | ~43 KB+ | 3+ JS/CSS files | No |
| django-scheduler (full) | ~200+ KB | CDN deps (Bootstrap + FC) | No |
| SimpleCalendarJS | ~14 KB | 2 files (JS + CSS) | No |
SimpleCalendarJS delivers month, week, and day event views at ~14 KB gzipped — roughly one-third the size of FullCalendar's core and a fraction of django-scheduler's full frontend stack. Two files in your static/ directory, zero external dependencies, zero framework coupling.
When to use django-scheduler instead
django-scheduler is the right choice when you need a complete calendar application, not just a calendar component:
- Recurring events with RRULE support — django-scheduler handles recurrence at the model level with an Occurrence API
- Multi-calendar support — separate calendars for different teams or categories, managed through Django models
- iCal export — built-in feed generation for subscribing from Google Calendar or Outlook
- Admin integration — manage events through Django's admin interface with no custom views
- Timezone handling — per-event timezone support baked into the models
If you need these backend features, django-scheduler's 1,300+ stars and active maintenance make it a solid choice. But if you already have your own event model and just need to display those events in an interactive calendar, adding an entire Django app with its own migrations is overhead you don't need.
Summary
- Django's built-in calendar tools are either date pickers (
AdminDateWidget) or static HTML tables (HTMLCalendar) — neither provides interactive event views - django-scheduler is a full calendar application with models, migrations, and 200+ KB of CDN-loaded frontend assets — powerful but heavy for simple use cases
- FullCalendar works in Django templates but ships ~43 KB+ gzipped and requires managing multiple JS/CSS files or CDN dependencies
- A vanilla JS calendar in a Django template needs only a
<div>, a{% static %}tag, and aDOMContentLoadedlistener — standard patterns every Django developer already knows - SimpleCalendarJS provides month, week, and day event views in ~14 KB gzipped with zero dependencies — two files in your
static/directory, a JSON view for events, and you're done - Use Django's
json_scriptfilter or a simpleJsonResponseview to feed model data to the calendar — no Django REST Framework required
Sources & Further Reading
Research & References
- Python HTML Calendar + Django Templating — Django Forum
- Best practices: Building a calendar view for large database — Django Forum
- django-scheduler — GitHub (llazzaro/django-scheduler)
- Django Calendar Packages Comparison — Django Packages
- FullCalendar integration with Django — GitHub (rodrigoamaral/django-fullcalendar)
- Django FullCalendar 6.1.11 + rrule — Medium (Azzouzhamza)
- How to manage static files in Django — Django Documentation
- Django's Built-in Admin Calendar Widget in Custom Forms — codegenes.net
- FullCalendar Pricing — fullcalendar.io
Image Credits
- Cover: Programming Code on Computer Screen — Pexels
All images free to use under the Pexels License.
Frequently Asked Questions
What is the best JavaScript calendar library for Django?
It depends on what you need. django-scheduler (1,300+ GitHub stars) bundles FullCalendar 6 and handles recurring events on the backend, but it pulls in Bootstrap 5, Bootstrap Icons, and FullCalendar via CDN — over 200 KB of frontend assets. For a lightweight event calendar with month, week, and day views, SimpleCalendarJS adds ~14 KB gzipped with zero dependencies and works in any Django template with a script tag and a div.
Can I use Django's built-in calendar widget outside the admin?
Technically yes — you can import AdminDateWidget from django.contrib.admin.widgets and use {{ form.media }} to inject the required JavaScript and CSS. But it's a date picker designed for admin forms, not an event calendar. It requires admin static files, depends on Django's bundled jQuery, and has no month/week/day event views.
Do I need jQuery for a calendar in Django?
No. Django's admin widgets depend on jQuery, and older versions of FullCalendar required it, but modern calendar libraries like SimpleCalendarJS are written in vanilla JavaScript with zero dependencies. You include the JS file, point it at a div, and it works.
How do I pass Django context data to a JavaScript calendar?
Use Django's built-in json_script template filter. In your view, pass a queryset serialized to a list of dicts. In the template, use {{ events|json_script:'event-data' }} to safely inject it as a JSON script tag. Then in JavaScript, read it with JSON.parse(document.getElementById('event-data').textContent).
Should I use CDN links or Django static files for calendar assets?
For production Django apps, use static files. Download or npm-install the library, place the dist files in your app's static/ directory, reference them with {% static %} tags, and run collectstatic for deployment. CDN links are fine for prototyping but add external dependencies, CORS considerations, and potential availability issues to production.
Is SimpleCalendarJS free to use in a Django project?
SimpleCalendarJS is free for personal and open-source projects. Commercial projects require a paid license — $49/year or $199 lifetime per project.
Add a calendar to your app today
Free for personal projects. $49/year or $199 lifetime per commercial project.
