Carkeek Events WP Registry grade A. No security issues foundWP RegistryNo security issues foundOpen the reportA
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/carkeek-studios/carkeek-events/archive/refs/heads/main.zipA lightweight, developer-friendly WordPress plugin for managing events. Registers Event, Location, and Organizer custom post types with classic meta boxes for structured data, a daily WP cron for expiring past events, optional Google Maps geocoding, a built-in Events Archive Gutenberg block, and integration with the carkeek-blocks custom-archive block.
The plugin provides no opinionated front-end styles. It ships default templates that you are expected to override in your theme.
Requirements
- WordPress 6.4+
- PHP 8.1+
Installation
- Drop the
carkeek-eventsfolder inwp-content/plugins/. - Activate the plugin in Plugins.
- Go to Events > Settings and click Flush Rewrite Rules once to register the CPT permalinks.
Post Types and Taxonomy
| Post Type | Archive Slug | Single Slug | Menu Icon |
|---|---|---|---|
carkeek_event |
/events/ |
/event/{slug}/ |
dashicons-calendar-alt |
carkeek_location |
— | /locations/{slug}/ |
dashicons-location |
carkeek_organizer |
— | /organizers/{slug}/ |
dashicons-groups |
Taxonomy: carkeek_event_category — hierarchical, registered to carkeek_event only. Archive slug: /event-category/{slug}/.
Meta Keys Reference
carkeek_event
| Meta Key | Type | Notes |
|---|---|---|
_carkeek_event_start |
string | ISO 8601 local time: YYYY-MM-DDTHH:MM:SS. Time is 00:00:00 when no time is set. |
_carkeek_event_end |
string | ISO 8601 local time. Empty for open-ended events (they will never be expired). |
_carkeek_event_location_id |
integer | Linked carkeek_location post ID. 0 if none. |
_carkeek_event_location_text |
string | Free-text location fallback when no CPT is linked. |
_carkeek_event_organizer_id |
integer | Linked carkeek_organizer post ID. 0 if none. |
_carkeek_event_organizer_text |
string | Free-text organizer fallback when no CPT is linked. |
_carkeek_event_website |
string | External registration or info URL. When set, templates render a CTA button. |
_carkeek_event_button_label |
string | CTA button label. Defaults to "Sign Up" at render time if blank. |
_carkeek_event_hidden |
boolean | Manual "Hide from calendar" flag. When set, the event is excluded from the Events Archive block and site search but stays published and viewable by direct link. Set via the editor's Event Options panel (block editor) or side box (classic). Never set automatically. |
carkeek_location
| Meta Key | Type | Notes |
|---|---|---|
_carkeek_location_address |
string | Street address |
_carkeek_location_city |
string | |
_carkeek_location_state |
string | State or province |
_carkeek_location_zip |
string | Zip or postal code |
_carkeek_location_country |
string | |
_carkeek_location_website |
string | URL |
_carkeek_location_lat |
string | Decimal latitude. Auto-populated by geocoding if API key configured. |
_carkeek_location_lng |
string | Decimal longitude. Auto-populated by geocoding if API key configured. |
carkeek_organizer
| Meta Key | Type | Notes |
|---|---|---|
_carkeek_organizer_email |
string | |
_carkeek_organizer_phone |
string | |
_carkeek_organizer_website |
string | URL |
Linking Locations & Organizers (Event editor)
The Location and Organizer fields in the Event Details meta box use a single combobox:
- Search or select — click the field and start typing to find an existing record (AJAX search of published locations/organizers).
- Create new — a persistent “+ Create new …” row at the bottom of the dropdown opens a blank inline form; the record is created and linked when you save the event.
- Inline edit — selecting an existing record reveals its current fields (address, etc.) inline, editable in place. Edits are saved to that shared record when you update the event, so a “Used by N events — edits apply to all” hint is shown. Locations include a Geocode Address button in the inline panel.
- Clear — the ✕ next to a selection removes the link (applied on save).
Inline edits to an existing record only save when the record’s fields loaded successfully and the current user can edit that record; otherwise the link is updated but the record’s data is left untouched.
Display Helpers (CarkeekEvents_Display)
All display helpers accept just the event post ID — they fetch their own meta internally. Output everything with wp_kses_post().
$post_id = get_the_ID(); // or $post->ID in a card template
$date_range = CarkeekEvents_Display::get_date_range_html( $post_id );
$location = CarkeekEvents_Display::get_event_location_html( $post_id );
$organizer = CarkeekEvents_Display::get_event_organizer_html( $post_id );
$event_link = CarkeekEvents_Display::get_event_link_html( $post_id );
get_date_range_html( $post_id, $separator = ', ' )
Returns the event date/time range as HTML with date and time values wrapped in separate <span> tags. This lets you control layout entirely through CSS without touching PHP.
<!-- Default output (same-day with time range) -->
<span class="carkeek-event-date">March 15, 2026</span>, <span class="carkeek-event-time">10:00 am – 2:00 pm</span>
<!-- Multi-day -->
<span class="carkeek-event-date">March 15, 2026</span>, <span class="carkeek-event-time">10:00 am</span>
–
<span class="carkeek-event-date">March 16, 2026</span>, <span class="carkeek-event-time">5:00 pm</span>
Display on two lines — pass '<br>' as the separator, or use CSS:
// PHP separator approach
$date_range = CarkeekEvents_Display::get_date_range_html( $post_id, '<br>' );
// CSS approach (works with default separator)
.carkeek-event-date,
.carkeek-event-time { display: block; }
Date and time formats respect the Date Format and Time Format settings under Events > Settings, falling back to WordPress site settings.
Filter: carkeek_events_date_range( $output, $start_date, $start_time, $end_date, $end_time )
Individual date/time getters
When you need to place the start/end date and time in separate markup, use the individual getters. Each returns a plain formatted string (escape at output with esc_html()), or empty string when that component is absent.
CarkeekEvents_Display::get_start_date( $post_id, $format = '' );
CarkeekEvents_Display::get_end_date( $post_id, $format = '' );
CarkeekEvents_Display::get_start_time( $post_id, $format = '' ); // '' for all-day events
CarkeekEvents_Display::get_end_time( $post_id, $format = '' );
$formatis an optional PHP date/time format override. When omitted, dates use the Date Format setting and times use the Time Format setting (falling back to the WordPress site settings) — the same formats asget_date_range_html().- Time getters return
''for all-day events (a start/end stored with no time component).
// Example: custom layout
printf(
'<time class="starts">%s at %s</time>',
esc_html( CarkeekEvents_Display::get_start_date( $post_id ) ),
esc_html( CarkeekEvents_Display::get_start_time( $post_id ) )
);
get_event_location_html( $post_id )
Returns the location display HTML per the Location Display setting in Events > Settings:
link(default) — location name linked to its CPT single pageaddress— formatted address block (name, street, city, state/zip/country)address_directions— address block with a "Get Directions" link to Google Maps
In the address and address_directions modes, the location name links to the location's website when one is set on the record (otherwise it is plain text).
Falls back to the free-text location string if no CPT post is linked.
Filter: carkeek_events_location_display( $html, $post_id )
get_event_organizer_html( $post_id )
Returns the organizer display HTML per the Organizer Display setting in Events > Settings:
link(default) — organizer name linked to their CPT single pageinfo— name + email (linked) + phone (linked) + website inline
In the info mode, the organizer name links to the organizer's website when one is set on the record (otherwise it is plain text).
Falls back to the free-text organizer string if no CPT post is linked.
Filter: carkeek_events_organizer_display( $html, $post_id )
get_event_link_html( $post_id )
Returns an <a> tag linking to the event's website/registration URL. The label comes from the Button Label meta field, defaulting to "Sign Up" when blank. Returns empty string if no URL is set.
The <a> tag includes the class wp-element-button so it inherits the theme's button styles automatically.
Filter: carkeek_events_link_html( $html, $post_id, $url, $label )
is_past_event( $post_id = null ) / is_upcoming_event( $post_id = null )
Boolean helpers for whether an event has ended — the equivalent of The Events Calendar's tribe_is_past_event(). $post_id defaults to the current post in the loop.
if ( CarkeekEvents_Display::is_past_event() ) {
echo '<span class="event-ended">This event has ended.</span>';
}
- Timed events are past once the current time passes the end datetime.
- All-day events (end with no time) are past only once the day after the end date begins.
- Open-ended events (no end date) are never past.
- Uses the site timezone (
wp_timezone()), so it is DST-correct.
Filter: carkeek_events_is_past_event( $is_past, $post_id )
get_add_to_calendar_html( $post_id, $args = array() )
Returns an "Add to Calendar" disclosure control (a native <details> element) with a Google Calendar link and a Download .ics link. Returns empty string when the event has no start date, when the Add to Calendar field is disabled, or for password-protected events. Optional $args: buttonLabel, googleLabel, icalLabel.
echo CarkeekEvents_Display::get_add_to_calendar_html( get_the_ID() );
See Add to Calendar for the block and the .ics endpoint.
get_location_string( $post_id )
Returns a plain-text location string (name + address, no HTML/links) for use in calendar links. Empty when Locations are disabled or none is set.
Low-level helpers (advanced use)
The convenience wrappers above call these underlying methods, which you can use directly when you need more control:
// format_date_range: pass raw meta values + optional separator
CarkeekEvents_Display::format_date_range( $start_date, $start_time, $end_date, $end_time, $separator );
// get_location_html: pass location ID + text fallback + event post ID
CarkeekEvents_Display::get_location_html( $location_id, $location_text, $post_id );
// get_organizer_html: pass organizer ID + text fallback + event post ID
CarkeekEvents_Display::get_organizer_html( $organizer_id, $organizer_text, $post_id );
Custom Templates
Single Event Page
The plugin ships two single-event templates and picks one based on the Editor setting (see Settings Reference):
- Block editor on (default):
single-carkeek_event-blocks.php— renders the title andthe_content()only, so theevent-details/event-date-time/ etc. blocks control the layout. If nocarkeek-events/event-detailsblock is present, it falls back to rendering the PHP meta header so dates/location/organizer/button are never lost. - Classic editor (Editor setting enabled):
single-carkeek_event.php— renders the plugin's default two-column layout: an "Events" tag + title, a meta column (date/time, location, organizer, registration button), a media column (featured image + optional Add to Calendar), and full-width content below. Labels, the date/time separator, the landing-page link, and the Add to Calendar toggle are all set under Events > Settings. Minimal structural CSS ships for the two-column grid (stacking on mobile) and can be disabled withadd_filter( 'carkeek_events_enqueue_single_css', '__return_false' ).
The classic template exposes these hooks: carkeek_events_single_title_block (filter — replace the whole tag+title), carkeek_events_before_title / carkeek_events_after_title, and carkeek_events_before_featured_image / carkeek_events_after_featured_image (actions, each passed $post_id).
Template hierarchy for the selected file (first match wins):
- Theme override:
{your-theme}/carkeek-events/{selected-file}.php - Plugin default:
carkeek-events/templates/{selected-file}.php - Disabled: If you set Single Event Template to "Use theme template" in Events > Settings, the plugin template is bypassed entirely and WordPress falls back to
single.phporsingular.php.
Filter to override the template path: carkeek_events_single_template( $template_path )
Minimal custom single-event template:
<?php
// Place this file at: {your-theme}/carkeek-events/single-carkeek_event.php
get_header();
while ( have_posts() ) :
the_post();
$post_id = get_the_ID();
$date_range = CarkeekEvents_Display::get_date_range_html( $post_id );
$location_html = CarkeekEvents_Display::get_event_location_html( $post_id );
$organizer_html = CarkeekEvents_Display::get_event_organizer_html( $post_id );
$event_link = CarkeekEvents_Display::get_event_link_html( $post_id );
?>
<article <?php post_class( 'my-event' ); ?>>
<h1><?php the_title(); ?></h1>
<?php if ( $date_range ) : ?>
<p class="event-dates"><?php echo wp_kses_post( $date_range ); ?></p>
<?php endif; ?>
<?php if ( $location_html ) : ?>
<div class="event-location"><?php echo wp_kses_post( $location_html ); ?></div>
<?php endif; ?>
<?php if ( $organizer_html ) : ?>
<div class="event-organizer"><?php echo wp_kses_post( $organizer_html ); ?></div>
<?php endif; ?>
<?php if ( $event_link ) : ?>
<div class="event-cta"><?php echo wp_kses_post( $event_link ); ?></div>
<?php endif; ?>
<div class="event-content">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
<?php get_footer(); ?>
Event Card (Archive / carkeek-blocks)
When the carkeek-blocks custom-archive block queries carkeek_event, the plugin intercepts the card template. Template hierarchy (first match wins):
- Filter:
carkeek_events_card_template( $template, $post, $attributes )— return a full file path - Theme override:
{your-theme}/carkeek-events/event-card/default.php - Plugin default:
carkeek-events/templates/event-card/default.php
Minimal custom event card template:
<?php
// Place this file at: {your-theme}/carkeek-events/event-card/default.php
if ( ! isset( $post ) ) {
$post = get_post();
}
$post_id = $post->ID;
$date_range = CarkeekEvents_Display::get_date_range_html( $post_id );
$location_html = CarkeekEvents_Display::get_event_location_html( $post_id );
$organizer_html = CarkeekEvents_Display::get_event_organizer_html( $post_id );
$event_link = CarkeekEvents_Display::get_event_link_html( $post_id );
?>
<div class="my-event-card">
<?php if ( has_post_thumbnail( $post_id ) ) : ?>
<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
<?php echo get_the_post_thumbnail( $post_id, 'medium' ); ?>
</a>
<?php endif; ?>
<a class="event-card__title" href="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
<?php echo esc_html( get_the_title( $post_id ) ); ?>
</a>
<?php if ( $date_range ) : ?>
<p class="event-card__dates"><?php echo wp_kses_post( $date_range ); ?></p>
<?php endif; ?>
<?php if ( $location_html ) : ?>
<div class="event-card__location"><?php echo wp_kses_post( $location_html ); ?></div>
<?php endif; ?>
<?php if ( $organizer_html ) : ?>
<div class="event-card__organizer"><?php echo wp_kses_post( $organizer_html ); ?></div>
<?php endif; ?>
<?php if ( $event_link ) : ?>
<div class="event-card__cta"><?php echo wp_kses_post( $event_link ); ?></div>
<?php endif; ?>
</div>
Filter and Action Hook Reference
Filters
| Hook | Args | Purpose |
|---|---|---|
carkeek_events_date_range |
$output, $start_date, $start_time, $end_date, $end_time |
Override formatted date range string |
carkeek_events_location_display |
$html, $post_id |
Override rendered location HTML |
carkeek_events_organizer_display |
$html, $post_id |
Override rendered organizer HTML |
carkeek_events_link_html |
$html, $post_id, $url, $label |
Override or wrap the CTA button HTML |
carkeek_events_query_args |
$args, $attributes |
Modify WP_Query args for the carkeek-blocks archive block |
carkeek_events_expiry_threshold |
$threshold, $post_id |
Override expiry date per event |
carkeek_events_card_template |
$template, $post, $attributes |
Supply a custom event card template path |
carkeek_events_single_template |
$template |
Override the single event template path |
carkeek_events_block_query_args |
$args, $attributes |
Modify WP_Query args for the Events Archive block |
carkeek_events_single_title_block |
$html, $post_id |
Replace the whole tag + title block on the single template |
carkeek_events_enqueue_single_css |
$enabled |
Return false to skip the plugin's structural single-template CSS |
Actions
| Hook | Args | Purpose |
|---|---|---|
carkeek_events_meta_box_after_dates |
$post |
Add fields after dates in the event meta box |
carkeek_events_meta_box_after_location |
$post |
Add fields after location in the event meta box |
carkeek_events_meta_box_after_organizer |
$post |
Add fields after organizer in the event meta box |
carkeek_events_meta_box_after_link |
$post |
Add fields after the website/button section |
carkeek_events_before_title / carkeek_events_after_title |
$post_id |
Single template — inject markup around the tag+title |
carkeek_events_before_featured_image / carkeek_events_after_featured_image |
$post_id |
Single template — inject content around the featured image |
carkeek_events_before_expire |
$post_id |
Fires before cron sets post_status = private |
carkeek_events_after_geocode |
$post_id, $lat, $lng |
Fires after geocoding completes |
Settings Reference
All settings are stored as a single array under the option key carkeek_events_settings.
| Key | Default | Notes |
|---|---|---|
content_expiry_days |
365 |
Integer, days. Events are set to private this many days after their end date. Minimum 1. |
disable_wp_archive |
1 |
1 = WP CPT archive disabled (use a custom Page + archive block). 0 = enabled. |
archive_slug |
events |
Slug for the WP CPT archive when enabled. Rewrite rules flush automatically on save. |
disable_block_editor |
0 |
1 = use the Classic editor for Events, Locations, and Organizers (also selects the classic single template). 0 = block editor (default; selects the block single template). |
use_plugin_template |
1 |
1 = use plugin template, 0 = use theme template |
date_format |
'' |
PHP date format string. Falls back to WP site setting. |
time_format |
'' |
PHP date format string. Falls back to WP site setting. |
location_display |
link |
link | address | address_directions |
organizer_display |
link |
link | info |
events_landing_url |
'' |
URL the "Events" tag links to on the single template. Relative or absolute. Blank → event archive link. |
datetime_label |
Date and Time |
Heading above the date/time (single template + Event Details block). Blank hides it. |
datetime_separator |
<br/> |
Between date and time. Only <br> allowed as markup (e.g. <br/>, ,, \|). |
location_label |
Location |
Heading above the location. Blank hides it. |
organizer_label |
Organizer |
Heading above the organizer. Blank hides it. |
show_add_to_calendar_single |
1 |
Show the Add to Calendar button on the single template (requires the Add to Calendar field enabled). |
use_locations |
1 |
1 = Location field is in use. 0 = hide the Location input in the editor and suppress location output on the front end and in the archive block. |
use_organizers |
1 |
1 = Organizer field is in use. 0 = hide the Organizer input and suppress organizer output. |
use_button |
1 |
1 = Registration button (event website URL + button label) is in use. 0 = hide those inputs and suppress the CTA button. Existing data is preserved. |
use_add_to_calendar |
1 |
1 = Add to Calendar control is available (block, template helper, and .ics endpoint). 0 = suppress the control and 404 the .ics endpoint. |
google_maps_api_key |
'' |
Stored server-side only. Never exposed to the browser. |
Expiry and Cron
A daily WP cron job (carkeek_events_daily_cron) manages event expiry automatically:
Expire: Events whose _carkeek_event_end is older than content_expiry_days (default 365) are set to post_status = private, returning 404 to visitors. No permanent deletion — posts remain in the database.
Events with no _carkeek_event_end are never expired.
Manual trigger:
wp cron event run carkeek_events_daily_cron
Or use the Run Expiry Check Now button in Events > Settings.
Events Archive Block
The plugin ships a native Gutenberg block — Carkeek Events Archive — available in the block inserter under Widgets.
Why use this instead of the WordPress CPT archive?
The WordPress archive template is hard to customise without a page builder. The archive block lets you place a filterable event list anywhere on any page, controlling layout, columns, and category filters from the block sidebar. Set Disable WordPress Archive in Events > Settings (the default), then create a regular Page with the slug events and drop the block on it.
Block options
| Option | Default | Notes |
|---|---|---|
| Number of Events | 6 |
Posts per page |
| Sort Order | ASC |
Ascending (upcoming first) or Descending (latest first) |
| Include Past Events | off | Show events whose end date has passed |
| Only Past Events | off | Show only past events |
| Filter by Category | off | Multi-select from carkeek_event_category terms |
| Post Layout | grid |
grid or list |
| Columns (Desktop/Tablet/Mobile) | 3/2/1 | Grid columns at each breakpoint |
| Display Featured Image | on | |
| Display Excerpt | off | |
| Excerpt Length | 25 words | |
| Show Pagination | off | |
| Show Hidden Events | off | Include events flagged "Hide from calendar" in this block. Off by default. |
| Hide Block When Empty | on | Returns empty string if no events match |
| Empty State Message | — | Shown when Hide When Empty is off |
Filter: carkeek_events_block_query_args( $args, $attributes ) — modify the WP_Query args for the block.
Build
The block source is in src/events-archive/. After installing npm dependencies, run:
npm install
npm run build
The compiled assets in build/events-archive/ are committed to the repo so the plugin works without a build step on deployment.
Add to Calendar
A single-event "Add to Calendar" control offering a Google Calendar link and a downloadable .ics (which also imports into Apple Calendar and Outlook desktop). Gated by the use_add_to_calendar setting (default on).
Three ways to use it:
- Template helper —
CarkeekEvents_Display::get_add_to_calendar_html( $post_id ). Rendered automatically in the plugin's single-event templates. - Block —
carkeek-events/add-to-calendar, insertable on Event posts. Configurable labels for the button and the two links. .icsendpoint — append?carkeek_ical=1to any event permalink to stream atext/calendardownload. Implemented as a query var (no rewrite rule, so no rewrite flush is ever needed). Returns 404 when the feature is disabled or the event is password-protected.
Details:
- Timezones: timed events are emitted in UTC (
…THHMMSSZ), all-day events useVALUE=DATEwith an exclusiveDTEND, both derived from the site timezone (wp_timezone()), so DST is handled correctly. - All-day = an event whose start has no time component (
00:00:00). - Calendar entry body = the event excerpt (or trimmed content) plus a "More info:" link back to the event.
- Hidden events (see the visibility feature) still serve their
.ics— the endpoint lives on the public permalink.
Geocoding
If a Google Maps API key is configured in Events > Settings, a "Geocode Address" button appears on Location edit screens. Clicking it sends the saved address fields to the Google Maps Geocoding API and populates the Latitude and Longitude fields automatically.
The API key is stored server-side only and never exposed to the browser or front end.
Releases
12 releases.
These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.