How to Add a Calendar to a WordPress Site Without a Plugin
By SimpleCalendarJS Team
You need an event calendar on your WordPress site. Every tutorial says the same thing: install The Events Calendar, or Modern Events Calendar, or one of a dozen plugins that create their own database tables, load 200+ KB of assets on every page, and tank your PageSpeed score. There's a better way. A vanilla JavaScript calendar loaded with wp_enqueue_script() gives you a full event calendar without touching the plugin ecosystem — and at a fraction of the performance cost.
The WordPress calendar plugin problem
WordPress calendar plugins try to be everything: event management systems, ticketing platforms, booking engines, and calendar displays — all bundled into one package. The result is predictable.
The Events Calendar — the most popular option with 800,000+ active installations — is a documented performance problem:
- It stores event dates as strings instead of indexed numbers, causing slow database queries on sites with many events (documented in their own performance guide)
- Recurring events create a separate post for every instance — one daily event for a year generates 365 database rows, each with its own metadata
- CSS and JavaScript load on every page of your site, not just the calendar page
- WordPress.org support threads report slow load times and high CPU usage during query execution
Here's what the popular options actually cost your site:
| Plugin | Gzipped JS+CSS | Database Impact | Loads Sitewide |
|---|---|---|---|
| The Events Calendar | ~200 KB+ | Custom tables + post-per-instance | Yes |
| Modern Events Calendar | ~180 KB+ | Custom tables | Yes |
| Sugar Calendar | ~60 KB | Lightweight custom tables | No |
| Vanilla JS calendar | ~14 KB | None | Only where used |
A vanilla JavaScript calendar adds zero database overhead and loads assets only on the pages where you place it.
Adding a calendar without a plugin
WordPress gives you two built-in functions for adding JavaScript properly: wp_enqueue_script() to load the file, and wp_localize_script() to pass data from PHP to JavaScript. That's all you need.
Here's the complete setup using SimpleCalendarJS — ~14 KB gzipped, zero dependencies:
1. Create a small custom plugin file
Using a plugin file instead of functions.php means your calendar survives theme changes. Create wp-content/plugins/simple-calendar-embed/simple-calendar-embed.php:
<?php /** * Plugin Name: Simple Calendar Embed * Description: Adds a lightweight JavaScript calendar via shortcode. * Version: 1.0 */ function sce_enqueue_assets() { if (!has_shortcode(get_post()->post_content ?? '', 'simple_calendar')) { return; } wp_enqueue_script( 'simple-calendar-js', 'https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.js', [], null, true ); wp_enqueue_style( 'simple-calendar-css', 'https://cdn.jsdelivr.net/npm/simple-calendar-js/dist/simple-calendar-js.min.css' ); } add_action('wp_enqueue_scripts', 'sce_enqueue_assets');
The has_shortcode() check ensures the library only loads on pages that actually use the calendar — not sitewide. This is the single biggest performance difference between this approach and a traditional plugin.
2. Register the shortcode
Add this to the same file:
function sce_shortcode($atts) { $atts = shortcode_atts([ 'view' => 'month', 'locale' => 'en-US', ], $atts); $calendar_id = 'sce-calendar-' . wp_unique_id(); wp_localize_script('simple-calendar-js', 'sceConfig_' . wp_unique_id(), [ 'containerId' => $calendar_id, 'eventsUrl' => rest_url('sce/v1/events'), 'nonce' => wp_create_nonce('wp_rest'), 'defaultView' => $atts['view'], 'locale' => $atts['locale'], ]); wp_add_inline_script('simple-calendar-js', sce_init_script($calendar_id, $atts)); return '<div id="' . esc_attr($calendar_id) . '"></div>'; } add_shortcode('simple_calendar', 'sce_shortcode'); function sce_init_script($id, $atts) { return " document.addEventListener('DOMContentLoaded', function() { var container = document.getElementById('" . esc_js($id) . "'); if (!container) return; new SimpleCalendarJs(container, { defaultView: '" . esc_js($atts['view']) . "', locale: '" . esc_js($atts['locale']) . "', enabledViews: ['month', 'week', 'day'], fetchEvents: async function(start, end) { var res = await fetch( '" . esc_url(rest_url('sce/v1/events')) . "' + '?from=' + start.toISOString() + '&to=' + end.toISOString(), { headers: { 'X-WP-Nonce': '" . wp_create_nonce('wp_rest') . "' } } ); return res.json(); }, onEventClick: function(event) { if (event.url) window.location.href = event.url; } }); }); "; }
Now you can place the calendar on any page or post with:
[simple_calendar]
Or customise the view and locale:
[simple_calendar view="week" locale="pt-BR"]
The shortcode works everywhere: the Gutenberg block editor (via a Shortcode block), Elementor, Divi, WPBakery, and any theme that supports shortcodes.
3. Add a REST API endpoint for events
function sce_register_rest_route() { register_rest_route('sce/v1', '/events', [ 'methods' => 'GET', 'callback' => 'sce_get_events', 'permission_callback' => '__return_true', 'args' => [ 'from' => ['required' => true, 'sanitize_callback' => 'sanitize_text_field'], 'to' => ['required' => true, 'sanitize_callback' => 'sanitize_text_field'], ], ]); } add_action('rest_api_init', 'sce_register_rest_route'); function sce_get_events($request) { $from = $request->get_param('from'); $to = $request->get_param('to'); $query = new WP_Query([ 'post_type' => 'event', 'posts_per_page' => 100, 'meta_query' => [ [ 'key' => 'event_start', 'value' => [$from, $to], 'compare' => 'BETWEEN', 'type' => 'DATETIME', ], ], ]); $events = []; foreach ($query->posts as $post) { $events[] = [ 'id' => $post->ID, 'title' => $post->post_title, 'start' => get_post_meta($post->ID, 'event_start', true), 'end' => get_post_meta($post->ID, 'event_end', true), 'color' => get_post_meta($post->ID, 'event_color', true) ?: '#3b82f6', 'url' => get_permalink($post), ]; } return rest_response($events); }
This uses a custom post type event with meta fields for dates — the standard WordPress pattern. The BETWEEN query returns only events within the visible date range, not the entire database.
If you're using an existing events custom post type or ACF fields, adjust the meta_query keys to match your field names.
Using static events without a REST API
Not every site needs a database-backed calendar. If your events change infrequently, you can hardcode them or pass them from WordPress directly:
function sce_static_shortcode() { $events = [ ['id' => 1, 'title' => 'Team Standup', 'start' => '2026-08-10T09:00:00', 'end' => '2026-08-10T09:30:00'], ['id' => 2, 'title' => 'Sprint Review', 'start' => '2026-08-14T14:00:00', 'end' => '2026-08-14T15:00:00'], ['id' => 3, 'title' => 'Release Day', 'start' => '2026-08-20T10:00:00', 'end' => '2026-08-20T18:00:00'], ]; $id = 'sce-static-' . wp_unique_id(); wp_add_inline_script('simple-calendar-js', " document.addEventListener('DOMContentLoaded', function() { var events = " . wp_json_encode($events) . "; new SimpleCalendarJs(document.getElementById('" . esc_js($id) . "'), { defaultView: 'month', events: events }); }); "); return '<div id="' . esc_attr($id) . '"></div>'; } add_shortcode('simple_calendar_static', 'sce_static_shortcode');
Zero API calls, zero database queries. The events are embedded in the page HTML and the calendar renders instantly.
Theming the calendar for your WordPress theme
SimpleCalendarJS uses CSS custom properties, so matching your WordPress theme takes a few lines in your stylesheet or the WordPress Customiser's Additional CSS:
.uc-calendar { --cal-primary: #0073aa; --cal-primary-dark: #005a87; --cal-today-bg: #f0f6fc; --cal-font-size: 14px; --cal-radius: 6px; } @media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #1e1e1e; --cal-text: #e0e0e0; --cal-border: #333; --cal-today-bg: #2c2c2c; } }
The --cal-primary: #0073aa matches WordPress's default admin blue. If your theme uses a different accent colour, swap that one variable and the entire calendar updates — buttons, selected dates, event highlights, and hover states.
Performance comparison: plugin vs. vanilla JS
To put this in perspective, here's what each approach adds to a page that displays a calendar:
| Metric | The Events Calendar | SimpleCalendarJS (vanilla) |
|---|---|---|
| JavaScript (gzipped) | ~150 KB | ~14 KB |
| CSS (gzipped) | ~50 KB | ~3 KB |
| Database queries | 15–40+ per page load | 1 REST call (async) |
| Loads on non-calendar pages | Yes (sitewide) | No |
| Custom database tables | Yes | None |
| PHP memory overhead | High (plugin bootstrap) | Minimal |
The difference is most visible on mobile. A ~200 KB plugin payload competes with your theme, WooCommerce, and other assets for bandwidth and parse time. A ~17 KB total (JS + CSS) calendar barely registers.
When a plugin is the right choice
There are valid reasons to use a WordPress calendar plugin:
- Ticketing and RSVPs: If you sell tickets or manage registrations, plugins like The Events Calendar Pro or Event Espresso handle payment processing, attendee management, and email confirmations — functionality that would take weeks to build from scratch
- Community submissions: If users need to submit events through the frontend, a plugin with form handling and moderation workflows saves significant development time
- iCal/Google Calendar sync: Some plugins sync bidirectionally with external calendar services. A vanilla JS calendar displays events — it doesn't manage subscriptions
- Non-technical editors: If your content team needs a drag-and-drop event editor in the WordPress admin, a plugin provides that UI. The vanilla JS approach requires events to be managed as custom posts or hardcoded
For most sites that need to display events on a page — business hours, class schedules, upcoming webinars, project timelines — a vanilla JavaScript calendar is simpler, faster, and avoids the plugin dependency chain entirely.
Summary
- WordPress calendar plugins load 200+ KB of assets sitewide and create custom database tables — even on pages without a calendar
- The Events Calendar stores dates as strings, creates a separate post per recurring instance, and has documented CPU and query performance issues
- A vanilla JavaScript calendar loaded with
wp_enqueue_script()adds ~17 KB total (JS + CSS), loads only on pages where the shortcode is used, and requires zero database tables - SimpleCalendarJS provides month, week, and day views at ~14 KB gzipped — install via CDN, register a shortcode, and point
fetchEventsat a WordPress REST API endpoint - The shortcode approach works with every page builder (Gutenberg, Elementor, Divi, WPBakery) and survives theme changes when placed in a custom plugin file
Sources & Further Reading
Research & References
- The Events Calendar is slow — WordPress.org Support
- Optimizing The Events Calendar Plugin: Resolving High CPU Usage — Atlantic BT
- Scaling and Performance for The Events Calendar — Official Knowledge Base
- How to Optimize Your WordPress Database — Codeable
- 75 Slow WordPress Plugins That Impact PageSpeed + CPU Usage — Online Media Masters
- wp_enqueue_script() — WordPress Developer Reference
- Enqueuing CSS or JavaScript — Learn WordPress
- The Best Events Calendar Alternative for WordPress — Pie Calendar
- How to Add Event Calendar to WordPress Without Plugin — Elfsight
Image Credits
All images free to use under the Pexels License.
Frequently Asked Questions
Can I add a calendar to WordPress without a plugin?
Yes. You can load a vanilla JavaScript calendar library via wp_enqueue_script() in your theme's functions.php or a small custom plugin file. The calendar renders client-side from a container div in your page template, shortcode, or block — no WordPress plugin dependency needed.
What is the lightest way to add an event calendar to WordPress?
The lightest approach is a vanilla JavaScript calendar loaded via CDN or npm. SimpleCalendarJS adds ~14 KB gzipped with month, week, and day views included. By comparison, The Events Calendar plugin loads 200+ KB of CSS and JavaScript sitewide and creates its own database tables.
Does The Events Calendar slow down WordPress?
Yes. The Events Calendar is a known performance bottleneck. It stores dates as strings instead of indexed numbers, creates separate posts for every recurring event instance (365 posts for one daily event), and loads its CSS and JavaScript on every page — not just the calendar page. Multiple WordPress.org support threads document slow query times and high CPU usage.
How do I pass WordPress data to a JavaScript calendar?
Use wp_localize_script() to pass PHP data (like a REST API endpoint URL or an array of events) to your enqueued JavaScript file. This creates a global JavaScript object that your calendar initialisation code can read — no inline PHP in your script tags needed.
Can I use a JavaScript calendar with a WordPress page builder?
Yes. Create a WordPress shortcode that outputs the calendar container div and enqueues the JavaScript. The shortcode works in Elementor, Divi, WPBakery, Beaver Builder, and the Gutenberg block editor via a Shortcode block. The calendar renders inside whatever layout the page builder creates.
Is SimpleCalendarJS free to use on a WordPress site?
SimpleCalendarJS is free for personal and open-source projects. Commercial WordPress sites (business sites, client projects, WooCommerce stores) 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.
