
How to Add an Event Calendar to a WordPress Website?
An event calendar gives visitors one obvious place to see what's coming up, whether that's a single conference date or a recurring weekly class schedule. Without one, event information tends to get scattered across blog posts, a PDF flyer, and whatever's pinned to the top of the homepage, none of which is easy to keep current or easy for a visitor to scan.
WordPress doesn't ship with event functionality out of the box, so you're choosing between a dedicated events plugin (which gets you calendar views, recurring events, and ticketing quickly) or a custom post type with a simple upcoming-events shortcode (which gets you exactly what you need and nothing else). This guide walks through both, with a complete, working code example for the custom route.
What to Decide Before You Start
A few questions will steer you toward the right approach:
- How many events, and how often do they repeat? A handful of one-off events is a very different problem from a class schedule that recurs every Tuesday.
- Do you need ticket sales? If yes, you almost certainly want a dedicated plugin rather than building payment handling yourself.
- Do you need a full month-grid calendar view, or is a simple upcoming-events list enough? A grid calendar is meaningfully more UI work than a list.
- Will events be submitted by other people (a community calendar), or only by you?
Option 1: Use an Events Plugin
The Events Calendar
The Events Calendar is the most widely used free events plugin for WordPress, and it's a genuinely solid choice for most sites. Out of the box it gives you:
- A dedicated Events custom post type with start/end date and time, venue, and organizer fields.
- Month, list, and day calendar views, each with its own shortcode and block.
- Recurring event support in the paid Events Calendar Pro add-on.
- Category and tag filtering for events, the same way posts have categories.
Setup:
- Install and activate The Events Calendar from Plugins > Add New.
- Go to the new Events menu and click Add New to create your first event, filling in the date, time, and venue.
- Use Events > Settings to choose which calendar views are enabled and to set your default view.
- Drop the
[tribe_events]shortcode on any page, or use the Events List or Events Calendar block, to display your events anywhere.
Modern Events Calendar
Modern Events Calendar is a strong alternative, particularly if you want a more visually customizable calendar out of the free version, including multiple calendar skins (grid, list, map view) and built-in booking fields for paid events. Setup mirrors The Events Calendar:
- Install and activate the plugin.
- Add events under the new MEC menu, including date, location, and an optional cost field.
- Place the
[MEC id="123"]shortcode (the ID refers to a saved calendar skin you configure under MEC > Shortcode) wherever you want the calendar to appear.
Both plugins are solid; The Events Calendar has the larger ecosystem of add-ons (WooCommerce ticketing, virtual events, filter bars), while Modern Events Calendar tends to look more polished out of the box without extra styling. If you haven't worked with plugins before, this rundown of what WordPress plugins are and how they work is a useful starting point before installing either one.
Option 2: Build a Custom Events List Yourself
If you just need a clean, chronological list of upcoming events (not a full interactive month grid), a custom post type plus a shortcode is a complete and maintainable solution.
Step 1: Register an "Event" Post Type
add_action( 'init', function () {
register_post_type( 'tw_event', [
'label' => 'Events',
'public' => true,
'has_archive' => false,
'show_in_rest' => true,
'supports' => [ 'title', 'editor', 'thumbnail' ],
'menu_icon' => 'dashicons-calendar-alt',
'rewrite' => [ 'slug' => 'events' ],
] );
} );
show_in_rest is set to true so the block editor's normal writing interface works for events, and each event gets its own page automatically via public => true.
Step 2: Add a Date and Time Meta Box
add_action( 'add_meta_boxes', function () {
add_meta_box(
'tw_event_details',
'Event Details',
'tw_render_event_meta_box',
'tw_event',
'side'
);
} );
function tw_render_event_meta_box( $post ) {
$date = get_post_meta( $post->ID, 'tw_event_date', true );
$time = get_post_meta( $post->ID, 'tw_event_time', true );
wp_nonce_field( 'tw_save_event_details', 'tw_event_nonce' );
?>
<p>
<label for="tw_event_date">Date</label><br>
<input type="date" id="tw_event_date" name="tw_event_date" value="<?php echo esc_attr( $date ); ?>" style="width:100%;">
</p>
<p>
<label for="tw_event_time">Time</label><br>
<input type="time" id="tw_event_time" name="tw_event_time" value="<?php echo esc_attr( $time ); ?>" style="width:100%;">
</p>
<?php
}
add_action( 'save_post_tw_event', function ( $post_id ) {
if ( ! isset( $_POST['tw_event_nonce'] ) || ! wp_verify_nonce( $_POST['tw_event_nonce'], 'tw_save_event_details' ) ) {
return;
}
if ( isset( $_POST['tw_event_date'] ) ) {
update_post_meta( $post_id, 'tw_event_date', sanitize_text_field( $_POST['tw_event_date'] ) );
}
if ( isset( $_POST['tw_event_time'] ) ) {
update_post_meta( $post_id, 'tw_event_time', sanitize_text_field( $_POST['tw_event_time'] ) );
}
} );
This gives every event a tw_event_date (YYYY-MM-DD) and tw_event_time meta field, editable right in the normal post editing screen.
Step 3: Build the Upcoming Events Shortcode
add_shortcode( 'upcoming_events', function ( $atts ) {
$atts = shortcode_atts( [
'count' => 5,
], $atts, 'upcoming_events' );
$events = new WP_Query( [
'post_type' => 'tw_event',
'posts_per_page' => (int) $atts['count'],
'meta_key' => 'tw_event_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => [
[
'key' => 'tw_event_date',
'value' => gmdate( 'Y-m-d' ),
'compare' => '>=',
'type' => 'DATE',
],
],
] );
if ( ! $events->have_posts() ) {
return '<p>No upcoming events right now.</p>';
}
ob_start();
?>
<ul class="tw-events-list">
<?php while ( $events->have_posts() ) : $events->the_post();
$date = get_post_meta( get_the_ID(), 'tw_event_date', true );
$time = get_post_meta( get_the_ID(), 'tw_event_time', true );
?>
<li class="tw-event-item">
<span class="tw-event-date">
<?php echo esc_html( date_i18n( 'M j, Y', strtotime( $date ) ) ); ?>
<?php if ( $time ) : ?>
· <?php echo esc_html( date_i18n( 'g:i a', strtotime( $time ) ) ); ?>
<?php endif; ?>
</span>
<a href="<?php the_permalink(); ?>" class="tw-event-title"><?php the_title(); ?></a>
</li>
<?php endwhile; ?>
</ul>
<?php
wp_reset_postdata();
return ob_get_clean();
} );
The key detail is the meta_query filtering on tw_event_date >= today, combined with 'type' => 'DATE', which makes WordPress compare the meta value as an actual date rather than as a plain string. Without 'type' => 'DATE', string comparison would sort "2026-2-1" before "2026-10-1", which is wrong; storing dates in YYYY-MM-DD format (as the HTML date input already does) avoids that trap entirely.
Drop [upcoming_events count="10"] onto any page and it renders a chronological list of the next ten events, automatically excluding anything already in the past. This is the same add_shortcode() pattern used for building a custom WordPress shortcode in general, just applied to a specific WP_Query.
Step 4: Add Simple CSS
.tw-events-list {
list-style: none;
margin: 0;
padding: 0;
}
.tw-event-item {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.75rem 0;
border-bottom: 1px solid #e5e5e5;
}
.tw-event-date {
color: #666;
white-space: nowrap;
}
Displaying a Real Month Grid
A list is enough for most sites, but if you genuinely need a visual month grid (clickable days, multiple events per day), that's meaningfully more front-end work: you'd need to group events by day, render a 7-column grid for the current month, and handle month navigation via URL query parameters or JavaScript. At that point, reaching for The Events Calendar or Modern Events Calendar is almost always faster than building and maintaining a grid calendar from scratch.
Choosing Between the Two Approaches
- Use a plugin if you need a real calendar grid view, recurring events, ticket sales, or venue/organizer taxonomies.
- Build it yourself if a simple chronological list of upcoming events covers your actual use case, and you'd rather avoid another plugin's settings and database tables.
- Combine both by using The Events Calendar for the heavy lifting and a small custom shortcode (using
tribe_get_events()instead of your own post type) for a stripped-down widget elsewhere on the site, like a homepage teaser.
Frequently Asked Questions (FAQ) About WordPress Event Calendars
Both are solid free plugins. The Events Calendar has a larger ecosystem of official add-ons (WooCommerce ticketing, virtual events, filter bars) and is more widely used, while Modern Events Calendar tends to offer more visual calendar skins out of the box without extra CSS work. Try both on a staging site if you're unsure.
Yes, both major plugins support ticket sales through official add-ons: Event Tickets (free, from the same developer as The Events Calendar) or Modern Events Calendar's built-in booking module. A custom-built calendar would need a payment gateway integration added on top of the event data.
Recurring events require the paid tier of either plugin (Events Calendar Pro or Modern Events Calendar Pro). Building true recurrence rules (weekly, monthly, "every second Tuesday") yourself is genuinely complex, since it needs to generate and expire individual occurrences, which is one of the strongest arguments for using a plugin over custom code here.
Both The Events Calendar and Modern Events Calendar support front-end event submission through dedicated add-ons or built-in forms, with moderation before an event goes live. A custom build would need its own front-end submission form plus a draft/publish moderation step, similar to how a custom booking form needs its own validation.
Any plugin adds some overhead, but both major events plugins only load their calendar CSS and JS on pages that actually display the calendar or an event, not site-wide. The custom shortcode approach above adds effectively no overhead beyond a single WP_Query and the list markup.
Filter your query so it only returns events on or after today's date. The custom shortcode above does this with a meta_query comparing tw_event_date to gmdate( 'Y-m-d' ) using the >= operator and 'type' => 'DATE'; both major plugins handle this automatically in their default calendar and list views.
Some events plugins, including certain Modern Events Calendar add-ons, support importing events from an external Google Calendar feed. This is worth considering if your events already live in a shared calendar elsewhere and you don't want to maintain the same information in two places.
Conclusion
For most sites with real recurring events, ticket sales, or a need for a proper month-grid view, a dedicated plugin like The Events Calendar or Modern Events Calendar will save far more setup time than it costs, and it handles genuinely fiddly problems (recurrence rules, timezone-aware date comparisons, ticket inventory) that are easy to get subtly wrong in custom code.
If your actual need is simpler, a short list of upcoming events with a date and a link, the custom post type and shortcode shown above is a complete, working solution you can maintain without any plugin dependency at all. Store dates as YYYY-MM-DD, query with 'type' => 'DATE' so sorting behaves correctly, and you have a chronological events list that updates itself automatically as dates pass.
Whichever route you choose, test the display on mobile before publishing. A calendar or events list that isn't readable on a phone screen defeats the point of putting it on the site at all.


