• Skip to main content
  • Skip to primary sidebar
  • Skip to footer

nSiteful Web Builders

Building a Better Web - One Site at a Time.

  • Home
  • About
    • Testimonials
    • Resources
    • Partner With Me
    • Frequently Asked Questions
  • Web Sites
  • Online Marketing
  • WordPress Support
    • Customized WordPress Training
    • 60-for-60 Sessions
  • Web Applications
  • Blog
    • At-A-Glance
    • Blog Articles Grouped by Category
    • Case Studies
    • General
    • Portfolio
    • Reviews
    • Snippets
    • Techniques
    • Projects
  • Contact
    • Purchase Retainer Consulting Hours
    • About Retainer Consulting Hours
    • Book a Meeting with Jeff
    • Tell Me About Your Web Project
    • nSiteful Newsletter Archives
    • nSiteful Rewards

By Jeff - 5/24/2022
Cats: Snippets · Tags: Custom Fields, Custom Post Types, WordPress

snippets

Custom Post Type: How to Create a Simple Custom Events Plugin

On This Page...

Toggle
  • The Scenario:
  • The Desired Result:
  • Code for registering the Events Custom Events Plugin Post Type:
  • Code for rendering Events landing page:
  • Code for fetching all posts for the Custom Events Plugin:
  • The All Events Loop
  • Comments/Questions?

Last updated May 9th, 2024 at 02:56 pm

The Scenario:

My client wants to display on his site information about trade shows at which his company will be exhibiting. I know there are lots of event plugins out there, but most of the ones I know about would be overkill for our needs. So, I created a must-use custom Events plugin for this client. One of the requirements was to show both current and past events and to segregate them on the main events “landing” page.

I tried making this happen using the standard WordPress archive page, but I couldn’t. I think this is because of the way the archive engine loops through the posts. Or maybe I just didn’t know how to do it. Anyway, I created a new page to be the acting archive page.

The Desired Result:

Here’s how the desired Events “archive” page looks on 5/24/22:

Events Landing Page

Note that current events (those whose end dates are on or after the current date) appear at the top of the page in ascending date order, while Past events appear at the bottom of the page in descending date order.

Code for registering the Events Custom Events Plugin Post Type:

I created a file called cpt-events.php in the mu-plugins folder, and here is the code for registering the “events” custom post type. (I used GenerateWP to generate some of this code, and then I did some search/replace in my editor to finish it up.)

<?php
function register_cpt_wbox_events() {
	$labels = array(
		'name'                  => _x( 'wanderBOX Event', 'Post Type General Name', 'text_domain' ),
		'singular_name'         => _x( 'Events', 'Post Type Singular Name', 'text_domain' ),
		'menu_name'             => __( 'Events', 'text_domain' ),
		'name_admin_bar'        => __( 'Events', 'text_domain' ),
		'archives'              => __( 'Event Archives', 'text_domain' ),
		'attributes'            => __( 'Event Attributes', 'text_domain' ),
		'parent_item_colon'     => __( 'Parent Event:', 'text_domain' ),
		'all_items'             => __( 'All Events', 'text_domain' ),
		'add_new_item'          => __( 'Add New Event', 'text_domain' ),
		'add_new'               => __( 'Add New', 'text_domain' ),
		'new_item'              => __( 'New Event', 'text_domain' ),
		'edit_item'             => __( 'Edit Event', 'text_domain' ),
		'update_item'           => __( 'Update Event', 'text_domain' ),
		'view_Event'             => __( 'View Event', 'text_domain' ),
		'view_Events'            => __( 'View Events', 'text_domain' ),
		'search_Events'          => __( 'Search Event', 'text_domain' ),
		'not_found'             => __( 'Not found', 'text_domain' ),
		'not_found_in_trash'    => __( 'Not found in Trash', 'text_domain' ),
		'featured_image'        => __( 'Featured Image', 'text_domain' ),
		'set_featured_image'    => __( 'Set featured image', 'text_domain' ),
		'remove_featured_image' => __( 'Remove featured image', 'text_domain' ),
		'use_featured_image'    => __( 'Use as featured image', 'text_domain' ),
		'insert_into_Event'      => __( 'Insert into Event', 'text_domain' ),
		'uploaded_to_this_Event' => __( 'Uploaded to this Event', 'text_domain' ),
		'Events_list'            => __( 'Events list', 'text_domain' ),
		'Events_list_navigation' => __( 'Events list navigation', 'text_domain' ),
		'filter_Events_list'     => __( 'Filter Events list', 'text_domain' ),
	);
	$rewrite = array('slug' => 'events');
	$args = array(
		'label'                 => __( 'Events', 'text_domain' ),
		'labels'                => $labels,
		'supports'              => array( 'title', 'editor', 'thumbnail', 'page-attributes' ),
		'taxonomies'            => array( 'event_cat', 'event_tag' ),
		'hierarchical'          => false,
		'public'                => true,
		'show_ui'               => true,
		'show_in_menu'          => true,
		'menu_position'         => 5,
		'show_in_admin_bar'     => true,
		'show_in_nav_menus'     => true,
		'can_export'            => true,
		'has_archive'           => false,
		'exclude_from_search'   => false,
		'publicly_queryable'    => true,
		'capability_type'       => 'post',
		'rewrite'				=> $rewrite,
	);
	register_post_type( 'wb_event', $args );
}
add_action( 'init', 'register_cpt_wbox_events', 0 );

Because I’m not using the standard/default WordPress archive page for this post type, I set the ‘has_archive’ argument to ‘false’ on line 46. I also added the ‘rewrite’ argument on line 50 to change the slug of the acting archive page to ‘events’ (see line 32) instead of ‘wb_event’, which is the actual post type.

Code for rendering Events landing page:

function wb_all_events_render($content) {
	// Fuggetaboutit if not proper page
	if ( ! is_page('events') ) return $content; 
	$date_format = 'n/j/Y'; // can be changed

	// *** CURRENT Events Loop
	
	// Call to get all CURRENT events. Send params for sort and which.
	$events = wb_all_events_get('asc', 'current');
	$content .= '<div class="events__current">';
	$content .= '<h2>Current Events</h2>';
	if ( ! $events ) {
		$content .= '<p>There are no current events to show. Please check back often.</p>';
	} else {
		$content .= '<p>Here are current and upcoming events. Click on image or event name to view details.</p>';
		$content .= '<div class="all-events__wrap">';
		$content .= wb_all_events_loop($events);
		$content .= '</div><!-- all-events__wrap -->';
		$content .= '</div><!-- events__current -->';
	}

	// *** PAST Events Loop

	$events = wb_all_events_get('desc', 'past');
	$content .= '<div class="events__past">';
	$content .= '<h2>Past Events</h2>';
	if ( ! $events ) {
		$content .= '<p>There are no past events to show.</p>';
	} else {
		$content .= '<p>Here are past events. Click on image or event name to view details.</p>';
		$content .= '<div class="all-events__wrap">';
		$content .= wb_all_events_loop($events);
		$content .= '</div><!-- all-events__wrap -->';
		$content .= '</div><!-- events__current -->';
	}
	return $content;
}
add_filter('the_content', 'wb_all_events_render');

Notes:

  • Line 3: If the slug of the current page is not ‘events’ (defined when I registered the CPT), then return the existing content.
  • Line 9: Here we’re in the Current events loop. So, this line calls the wb_all_events_get function (see below), with two parameters: ‘sort order’ (in this case ‘asc’) and ‘which’ (in this case ‘current’).
  • Line 24: Here we’re in the Past events loop. So, this line calls the wb_all_events_get function with the ‘sort order’ parameter set to ‘desc’ and the  ‘which’ parameter set to ‘past’.
  • Line 32: This is where the meat comes in. See below.
  • Line 38: I’m filtering the ‘the_content’ filter hook.

Code for fetching all posts for the Custom Events Plugin:

This is the code for fetching and returning the events that are rendered by the function displayed above.

<?php
function wb_all_events_get($sort_order, $which) {
	$date_compare = date('Y-m-d');
	
	// Operator must be changed depending on whether we're looking for 'current' or 'past' events.
	// For 'current', we want end_date >= today; for 'past', we want end_date < today.
	switch ( $which ) {
		case 'current':
			$compare = '>=';
		break;
		case 'past':
			$compare = '<';
		break;
	}
	$args = array(
		'post_type' => 'wb_event',
		'meta_query' => array(
			'key' 		=> 'end_date',
			'compare' 	=> $compare,
			'value' 	=> $date_compare,
			'type' 		=> 'DATETIME',
		),
		'meta_key'		=> 'start_date',
		'orderby'		=> 'meta_value',
		'order' 		=> $sort_order,
		'numberposts' => -1,
	);
	$event_posts = get_posts($args);
	return $event_posts;
}

Notes:
This function uses the WordPress meta_query class, which allows filtering results by object metadata. (I’m using Advanced Custom Fields Pro for the custom fields.)

  • Line 2: Note the arguments $sort_order and $which. This variable appears on line 25; the $which value is used to set the $compare variable, used in the switch section below.
  • Line 3: The $date_compare variable sets the date to which each event’s end_date will be compared (in line 19). While I was testing my code, I changed this value several times to make sure the Current/Past logic was working properly.
  • Line 7: The switch operator, inside of which I’m using the meta_query class.
  • Line 28: Call to the WordPress get_posts function, using the arguments defined in lines 16-26.

The All Events Loop

Here is the code for displaying the events — it’s the meat from lines 17 and 32 of the code above.

function wb_all_events_loop($events) {
	$date_format = 'n/j/Y';
	$output = '';
	foreach ($events as $event ) {
		$id = $event->ID;
		$event_title 	= $event->post_title;
		$location 		= get_post_meta($id, 'location_name', true);
		$city 			= get_post_meta($id, 'city', true);
		$state 			= get_post_meta($id, 'state', true);
		$start_date 	= get_post_meta($id, 'start_date', true);
		$end_date 		= get_post_meta($id, 'end_date', true);
		$thumb 			= get_the_post_thumbnail( $id, 'medium', array( 'class' => 'alignright'));
		$title_attribute = 'View Details about ' . $event->post_title;
		
		$output .= '<div class="event__wrap">';
			$output .= '<div class="event__featured-image"><a title="' . $title_attribute .  ' " href="' . get_the_permalink($id) . '">' . $thumb . '</a></div>';
			$output .= '<h2 class="event__title"><a title="'.$title_attribute.'" href="'.get_the_permalink($id).'">'. $event_title . '</a></h2>';
			$output .= '<div class="archive__event-meta">';
				//$content .= '<div>'. $location . '</div>';
				$output .= '<div class="city-state">'. $city . ', ' . $state . '</div>';
				$output .= '<div class="event-date">'. 
				date($date_format, strtotime($start_date)) . ' - ' . 
				date($date_format, strtotime($end_date)) . '</div>';
			$output .= '</div>';
		$output .= '</div>';
	}
	return $output;
}

The function receives the array of events (past or current) and then loops through each event, grabbing the value of each custom field using the WordPress get_post_meta() function. Then the output is assembled and returned to the calling function.

Comments/Questions?

I welcome your comments and questions.

Like what you see? Share with others and join my mailing list. No long-term commitment, unsubscribe any time.

Related Posts

  1. MemberPress: Create a Custom Shortcode to Display a MemberPress Membership Price ANYWHERE on Your Website
  2. Offer Live Chat for Enhanced Customer Engagement and Sales
  3. Dive Into WordPress Custom Post Types – Part 3
  4. Automatic “Visit Web Site” Links on WordPress Portfolio Posts
  5. PayPal Phishing Scam Alert Oct 11, 2018

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Primary Sidebar

mailchimp signup

Subscribe to get notified when new articles are published. Unsubscribe any time. No spam. I promise. Check out my newsletter archives.

social

Twitter Facebook LinkedIn

Recent Articles

  • Post-Specific CSS for WordPress October 5, 2026
  • You built an online course, but they haven’t come. September 16, 2026
  • Custom MemberPress Pricing Pages December 19, 2025
  • CSS Data Attribute Override December 11, 2025
  • MemberPress Rules Using Custom Taxonomies and Custom Roles December 10, 2025

Filter By Category/Tag

Categories

  • Case Studies (8)
  • For Staff (1)
  • General (73)
  • Portfolio (7)
  • Reviews (14)
  • Snippets (24)
  • Techniques (52)

Popular Tags

Advanced Custom Fields Blogging Child Themes Content Marketing CSS Customer Service Custom Fields Custom Post Types Diagnostics Domain Names Facebook FooGallery Genesis Hosting HTML Images iPhone Libra Live Chat Marketing Media MemberPress MemberPress Courses Membership Sites Mobile-Friendly MySQL Photo Gallery php Pinterest Plugins Post Formats Pricing Project Management SEBA SEO Seth Godin Shortcodes Social Networking Surveys Taxonomies Twitter Video Web design Web forms WordPress

Footer

Background

Web Sites | WordPress Support | Web Applications.

Formally trained in liberal arts and education (I have a B.A. in Government from Harvard and studied Secondary Education at Rutgers Graduate School), I have honed my skills in the communication arts and sciences as a teacher, trainer, instructional designer, writer, photographer, calligrapher, helpdesk manager, database programmer, and multimedia developer.

(I've also been a group counselor, waiter, bartender, bicycle messenger boy, computer salesman, carpenter's helper, financial analyst, and school board president.)

Tech

Systems since 1983.
Web sites since 1994.
PHP since 2001.
WordPress since 2007.

Contact

Book Meeting
770-772-5134
Email Jeff
Send Money
All Ways

Copyright 2026, nSiteful Web Builders, Inc.

Cookies Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
View preferences
  • {title}
  • {title}
  • {title}
https://iframe.mediadelivery.net/embed/392008/42d18bc1-2adc-4741-b733-053d08d09c32
https://vz-000c5976-3ab.b-cdn.net/42d18bc1-2adc-4741-b733-053d08d09c32/play_720p.mp4

Receive occasional emails from Jeff