Last updated July 11, 2026, Version 0.34
GatherPress uses WordPress post type supports to allow developers to enable GatherPress features on their own custom post types. This makes it possible to use event dates, venues, RSVPs, and other GatherPress functionality without being limited to the built-in gatherpress_event or gatherpress_venue post types.
Event Post Type Supports
These supports are declared on post types that act as events.
gatherpress-event-date
The core identifier for event post types. Enables event datetime storage and display. This includes:
- Registration of datetime meta fields (
gatherpress_datetime,gatherpress_datetime_start,gatherpress_datetime_end,gatherpress_timezone, etc.) โ GatherPress also auto-adds WordPress’scustom-fieldssupport to the post type so the REST controller actually attaches themetafield to the schema (without it,register_post_meta()quietly registers the keys but the editor’s PUT silently strips them) - Storage in the
gatherpress_eventsdatabase table - Date-based query ordering (upcoming/past)
- Event Date block rendering
- Add to Calendar block rendering
- RSS feed enrichment with event date information
- Post date override with event date (when enabled in settings)
Usage for gatherpress-event-date
Declare the support inside your register_post_type() call:
register_post_type( 'my_custom_event', array(
'supports' => array( 'title', 'editor', 'gatherpress-event-date' ),
// ... other args
) );
Declare supports on registration, not after. GatherPress wires its meta registration, admin-list columns, REST filters, and other per-post-type hooks on the
registered_post_typeaction โ i.e. at the moment your post type finishes registering. Callingadd_post_type_support( 'my_custom_event', 'gatherpress-event-date' )afterregister_post_type()will makepost_type_supports()return true, but GatherPress’s internal wiring won’t run for your post type. Always include the support in thesupportsarray.
Once registered, you can use the Event class with your custom post type:
use GatherPress\Core\Event;
$event = new Event( $my_custom_post_id );
$event->save_datetimes( array(
'post_id' => $my_custom_post_id,
'datetime_start' => '2025-06-15 10:00:00',
'datetime_end' => '2025-06-15 12:00:00',
'timezone' => 'America/New_York',
) );
Relabeling the date column and editor panel
The default “Event date & time” admin column header and “Event settings” sidebar panel title can be relabeled per post type without re-implementing either surface. The column key (datetime) and panel name stay the same โ only the visible label changes.
// Relabel the admin list column for a "production" post type.
add_filter(
'gatherpress_event_datetime_label',
function ( string $label, string $post_type ): string {
return 'production' === $post_type ? __( 'Premiere date', 'my-plugin' ) : $label;
},
10,
2
);
// Relabel the editor sidebar panel title for the same post type.
import { addFilter } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';
addFilter(
'gatherpress.eventSettingsPanelTitle',
'my-plugin/production-panel-title',
( title, postType ) =>
'production' === postType ? __( 'Production settings', 'my-plugin' ) : title
);
Surfacing your own labels in GatherPress UI
GatherPress’s settings sub-menus and a handful of admin UI strings now pull from each post type’s registered labels rather than hardcoded “Event”/”Venue” copy. Whatever label you register your custom event-supporting post type with โ singular_name, name, etc. โ is what shows up.
register_post_type( 'my_custom_event', array(
'labels' => array(
'name' => __( 'Happenings', 'my-plugin' ),
'singular_name' => __( 'Happening', 'my-plugin' ),
),
'supports' => array( 'title', 'editor', 'gatherpress-event-date' ),
) );
If you’d rather rename the labels of GatherPress’s own gatherpress_event (or gatherpress_venue), use WordPress’s post_type_labels_<post_type> filter โ the labels propagate to the same UI surfaces.
When writing your own admin UI on top of GatherPress, read labels through Utility::post_type_label( $key, $post_type ). It wraps get_post_type_object() and returns an empty string when the post type isn’t registered (or the label key isn’t set), so call sites don’t have to defend against either.
Bare archive temporal handling
The bare post-type archive URL (e.g. /my_custom_event/) defaults to upcoming for every event-supporting post type, so past entries don’t appear alongside future ones in the same list. Two knobs override that default:
- URL parameters: appending
?gatherpress_event_query=upcoming(orpast) narrows that page load to the matching subset. - The
gatherpress_event_archive_modefilter receives the queried post type as its second argument and lets you pin a different mode for any event-supporting post type. Valid return values areupcoming,past, ornoneโ anything else is coerced back toupcoming. Returningnoneopts the archive out entirely (404).
add_filter(
'gatherpress_event_archive_mode',
function ( string $mode, string $post_type ): string {
if ( 'my_custom_event' === $post_type ) {
return 'past';
}
return $mode;
},
10,
2
);
The standard gatherpress_event post type also lets the Event Archive setting choose the default before the filter runs.
gatherpress-rsvp
Enables the comment-based RSVP system for a post type. This includes:
- RSVP response tracking (attending, not attending, waiting list)
- Attendee management and waiting list processing
- RSVP blocks rendering (rsvp, rsvp-form, rsvp-response, rsvp-template)
- RSVP token-based email verification for anonymous attendees
- Comment count adjustment to reflect RSVP activity
- The RSVPs column (and its sortable header) on the admin list table, replacing the standard comments column to avoid confusion with RSVP submissions
- The “RSVP settings” panel in the block editor sidebar (guest limit, max attendance, anonymous RSVP, per-event toggle)
- The post-publish “Send an event update via email” notice that opens the attendee email composer
A post type that declares gatherpress-event-date without gatherpress-rsvp (e.g. a “production” post type that just wants a premiere date) keeps the datetime column and Event settings sidebar but gets none of the RSVP UI above.
Usage for gatherpress-rsvp
register_post_type( 'my_custom_event', array(
'supports' => array( 'title', 'editor', 'gatherpress-event-date', 'gatherpress-rsvp' ),
// ... other args
) );
gatherpress-venue
Enables physical venue association for a post type. This includes:
- Wiring the venue’s shadow taxonomy (e.g.
_gatherpress_venue) onto the post type so events can be tagged with their venue term - Venue selector in the block editor
- Venue block rendering (name, address, map, phone, website)
- Venue detail field visibility (hides empty address/phone/website blocks)
The shadow taxonomy itself is registered by the gatherpress-shadow-source primitive; declaring gatherpress-venue is what wires it onto the event post type.
Usage for gatherpress-venue
register_post_type( 'my_custom_event', array(
'supports' => array( 'title', 'editor', 'gatherpress-event-date', 'gatherpress-venue' ),
// ... other args
) );
You can also override the venue post type used for lookups via the gatherpress_venue_post_type filter. The filter receives the event post type as a second argument, enabling per-event-type venue post type overrides:
add_filter( 'gatherpress_venue_post_type', function( $post_type, $event_post_type ) {
if ( 'my_custom_event' === $event_post_type ) {
return 'my_custom_venue';
}
return $post_type;
}, 10, 2 );
gatherpress-online-event
Enables online event functionality for a post type. This includes:
- Online event toggle and link field in the block editor inspector
- Online Event block rendering (icon and link)
- Association with the
online-eventterm in the_gatherpress_venuetaxonomy
Usage for gatherpress-online-event
register_post_type( 'my_custom_event', array(
'supports' => array( 'title', 'editor', 'gatherpress-event-date', 'gatherpress-online-event' ),
// ... other args
) );
Venue Post Type Supports
These supports are declared on post types that act as venues. gatherpress-venue-information is the core identifier โ declaring it is what makes a post type a venue source.
gatherpress-venue-information
The core identifier for venue post types. Enables venue address and contact data. This includes:
- Registration of five individual editor-writable post meta keys, each
show_in_restand bindable viacore/post-metablock bindings:gatherpress_addressgatherpress_latitudegatherpress_longitudegatherpress_phonegatherpress_website
- Registration of eight server-populated structured-address meta keys, each
show_in_restfor read access (REST writes are stripped, since these are derived fromgatherpress_addressby an async geocode cron handler that fires only when the address actually changes):gatherpress_house_numbergatherpress_streetgatherpress_citygatherpress_countygatherpress_stategatherpress_postcodegatherpress_countrygatherpress_country_code
- Venue detail blocks (address, phone number, website)
- Implicit declaration of
gatherpress-shadow-source, which registers the_<post_type>taxonomy and keeps one term per venue post in sync with the post slug post_type_supports( $type, 'gatherpress-venue-information' )is the canonical check for “is this a venue?”
Meta revisions are enabled automatically when your venue post type declares revisions in its supports array; venue post types that opt out of revisions still get the meta registered without revisions_enabled.
Meta registration itself lives on GatherPress\Core\Venue\Meta::register(). The companion field-list constants are Venue\Meta::EDITOR_WRITABLE_FIELDS (the five editor-writable suffixes) and Venue\Meta::STRUCTURED_ADDRESS_FIELDS (the eight Photon-derived suffixes) โ those are the single source of truth for registration, REST stripping, the geocode cron write loop, and Venue::get_information(). The matching event-side class is GatherPress\Core\Event\Meta.
Structured-address fields
The eight structured-address fields are populated by a server-side cron handler that runs on a 5-second delay after gatherpress_address changes. Manual edits to those fields via update_post_meta() from trusted server code are preserved as long as the address itself doesn’t change. To suppress the outbound HTTP-on-save (firewalled installs, dev environments without Photon access), return false from the gatherpress_geocode_on_save_enabled filter. To replace WP-Cron with a different scheduler (e.g. Action Scheduler), short-circuit the gatherpress_async_geocode_pre_enqueue_job filter with any non-null value.
The address autocomplete and save-time reverse-geocode that drive these fields go through two REST endpoints (/gatherpress/v1/geocode and /gatherpress/v1/geocode/search), both of which share a per-user fixed-window rate limit. The default ceiling is 30 requests per 60 seconds; the (N+1)th request returns HTTP 429 Too Many Requests with a Retry-After header. Lower or raise the ceiling via the gatherpress_geocode_rate_limit_per_minute filter (values below 1 are clamped to 1). To disable the rate limit entirely โ for example when a CDN / WAF already covers this surface โ return false from gatherpress_geocode_rate_limit_enabled.
Usage for gatherpress-venue-information
register_post_type( 'my_custom_venue', array(
'supports' => array( 'title', 'editor', 'gatherpress-venue-information' ),
// ... other args
) );
Block bindings
Because each field is its own meta key, you can bind core blocks (paragraph, heading, button, etc.) to a venue field directly without an intermediate JSON parse step. For example, a paragraph bound to the venue’s full address:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"gatherpress_address"}}}}} -->
<p></p>
<!-- /wp:paragraph -->
gatherpress-venue-map
Enables map display for a venue post type. This includes:
- Registration of map meta fields (
gatherpress_map_show,gatherpress_map_zoom,gatherpress_map_height) - Venue Map block rendering
Usage for gatherpress-venue-map
register_post_type( 'my_custom_venue', array(
'supports' => array( 'title', 'editor', 'gatherpress-venue-information', 'gatherpress-venue-map' ),
// ... other args
) );
Shared Primitives
These supports aren’t specific to events or venues โ they expose foundational behaviors that any post type can opt into.
gatherpress-shadow-source
Registers a hidden _<post_type> taxonomy for the post type and keeps one term per published post in lockstep with the post’s slug and title. Sometimes called a “shadow taxonomy” โ the term mirrors the post and lets consumers (events, sessions, productions, etc.) tag themselves with that term to model a relationship.
This is the primitive that powers gatherpress_venue โ event tagging. gatherpress-venue-information implicitly declares gatherpress-shadow-source, so existing venue post types pick up the lifecycle without changes. Companion plugins can declare it directly on their own post types โ productions, organizers, sponsors โ to get the same behavior with no venue-specific baggage.
This support includes:
- A hidden taxonomy
_<post_type>registered withshow_ui => false,show_admin_column => true,publicly_queryable => true,show_in_rest => true, andrewrite => false(so the taxonomy appears in Query Loop block taxonomy controls but doesn’t expose public archive URLs) - Labels inherited from the source post type’s
name/singular_name(override via thegatherpress_shadow_taxonomy_argsfilter) - A
save_post_<post_type>hook that inserts a term on first publish, with the term slug derived from the post’spost_nameprefixed with an underscore (e.g.my-productionโ_my-production) - A
post_updatedhook that updates the term’s name and slug whenever the source post is renamed - A
delete_post_<post_type>hook that removes the term when the source post is deleted
Sentinel terms (terms that don’t carry a leading underscore, such as the venue subsystem’s online-event) are deliberately preserved โ Shadow_Source::is_shadow_term_slug() is the canonical predicate for distinguishing real shadow terms from sentinels.
Usage for gatherpress-shadow-source
register_post_type( 'production', array(
'supports' => array( 'title', 'editor', 'gatherpress-shadow-source' ),
// ... other args
) );
Wiring the resulting taxonomy onto consumer post types is done via the gatherpress_shadow_taxonomy_object_types filter โ declare which event post types should be tagged with your shadow source and Shadow_Source handles the register_taxonomy_for_object_type() call for you. Default is an empty list, so wiring is always explicit (and visible in the auto-generated hook reference).
add_filter( 'gatherpress_shadow_taxonomy_object_types', function ( array $object_types, string $source_post_type ): array {
if ( 'production' === $source_post_type ) {
$object_types[] = 'gatherpress_event';
}
return $object_types;
}, 10, 2 );
The venue subsystem uses this same filter to wire _gatherpress_venue onto every gatherpress-venue-supporting event CPT โ Venue\Setup::attach_venue_taxonomy_to_event_types() is the canonical reference implementation.
If you need to bypass the filter (for example to attach to a non-event post type), register_taxonomy_for_object_type() still works as a manual escape hatch โ but the filter is the discoverable idiom and should be preferred.
Pairing with the gatherpress/venue block
The gatherpress/venue block accepts a sourcePostType attribute (default gatherpress_venue). Set it to your shadow-source CPT and the block resolves its connected source post via your taxonomy โ same block, same field-rendering inner blocks (post-title, gatherpress/venue-detail, etc.), different source. Example for a production-detail surface on an event template:
<!-- wp:gatherpress/venue {"sourcePostType":"production"} -->
<!-- wp:post-title /-->
<!-- /wp:gatherpress/venue -->
The gatherpress/event-query block’s “Filter by Current Venue” contextual toggle automatically scopes to whatever shadow-source CPT the queried page represents โ on a production singular it scopes to that production’s events, on a tour singular to that tour’s events, etc. The toggle label adapts in the editor via usePostTypeLabel, so users editing a Tour template see “Filter by Current Tour”.
Reusing the shadow-source query primitives
If you’re building a custom Query Loop block that needs the same “scope to the current shadow-source page” behavior, two public methods on Shadow_Source do the heavy lifting:
use GatherPress\Core\Shadow_Source;
// Inside a pre_get_posts callback:
$shadow_source = Shadow_Source::get_instance();
$source_post = $shadow_source->resolve_post_from_query_context( $query );
if ( $source_post instanceof WP_Post ) {
$tax_query = (array) ( $query->get( 'tax_query' ) ?: array() );
$tax_query[] = $shadow_source->build_tax_query_clause( $source_post );
$query->set( 'tax_query', $tax_query );
}
resolve_post_from_query_context() handles both resolution paths โ the frontend is_singular() path AND the REST editor-preview path. For the REST path to work, the block’s JS must write the editor’s current post id + post type into the query attributes as gatherpress_shadow_source_post_id and gatherpress_shadow_source_post_type when the toggle is on. Those are the only two query vars the resolver looks at โ everything else flows from the resolved post.
Customizing the taxonomy registration
To override labels, REST visibility, or other taxonomy registration args:
add_filter( 'gatherpress_shadow_taxonomy_args', function( $args, $post_type ) {
if ( 'production' === $post_type ) {
$args['labels']['name'] = __( 'Productions', 'my-plugin' );
$args['labels']['singular_name'] = __( 'Production', 'my-plugin' );
}
return $args;
}, 10, 2 );
How It Works
GatherPress replaces hardcoded post type checks with post_type_supports() calls. For example, instead of:
// Before: only works with gatherpress_event.
if ( Event::POST_TYPE === get_post_type( $post_id ) ) {
// Handle event date logic.
}
GatherPress now uses:
// After: works with any post type that has gatherpress-event-date support.
if ( post_type_supports( get_post_type( $post_id ), 'gatherpress-event-date' ) ) {
// Handle event date logic.
}
The same pattern applies on the venue side:
// Before: only works with gatherpress_venue.
if ( Venue::POST_TYPE === get_post_type( $post_id ) ) {
// Handle venue logic.
}
// After: works with any post type that has gatherpress-venue-information support.
if ( post_type_supports( get_post_type( $post_id ), 'gatherpress-venue-information' ) ) {
// Handle venue logic.
}
Similarly, queries that previously targeted only gatherpress_event or gatherpress_venue now include all post types with the relevant support:
// Event queries.
$args = array( 'post_type' => get_post_types_by_support( 'gatherpress-event-date' ) );
// Venue queries.
$args = array( 'post_type' => get_post_types_by_support( 'gatherpress-venue-information' ) );
In JavaScript, support checks go through one of two helpers in src/helpers/event.js:
import {
isPostTypeSupporting,
usePostTypeSupports,
} from '../../helpers/event';
// Outside React: imperative check that reads the post-type registry once.
isPostTypeSupporting( 'gatherpress-event-date', postType );
// Inside a React component: reactive check via useSelect, so the component
// re-renders the moment the post-type definition resolves.
const isEvent = usePostTypeSupports( 'gatherpress-event-date', postType );
Always reach for usePostTypeSupports when the result drives rendering โ opacity, visibility, conditional inspector controls, etc. The non-reactive isPostTypeSupporting reads select('core').getPostType(...) directly, and the post-type registry usually isn’t cached on first render. If a dim gate is wired through the non-reactive helper, the gate resolves to false on the first paint and the component never re-renders once supports load โ leaving the block permanently dimmed in Query Loops.
For blocks that gate dimming on both context support and data presence, hasValidBlockContext in src/helpers/editor.js accepts a pre-computed hasSupport boolean โ pass the result of usePostTypeSupports:
const hasSupport = usePostTypeSupports( 'gatherpress-venue', context?.postType );
const blockProps = useBlockProps( {
style: {
opacity: hasValidBlockContext( {
isDescendentOfQueryLoop,
hasSupport,
hasData: hasVenue,
} ) ? 1 : DISABLED_FIELD_OPACITY,
},
} );
Reading the post type from block context (context?.postType) requires postType to be declared in the block’s block.json usesContext array โ otherwise context.postType will be undefined inside a Query Loop’s Post Template even when the queried post type would carry the relevant supports.
Naming Convention
All GatherPress supports use the following naming convention:
- Kebab-case to match WordPress core conventions (e.g.,
custom-fields) gatherpress-prefix to avoid conflicts with other plugins
Important Notes
- The
gatherpress_eventsdatabase table stores data bypost_idand is post-type agnostic. Any post type withgatherpress-event-datesupport can store datetime data in this table. - Declare supports in your
register_post_type()call, not via a lateradd_post_type_support(). GatherPress registers its per-post-type hooks (meta fields, admin-list columns, REST query filters, venue save hooks) on theregistered_post_typeaction, which only fires whenregister_post_type()runs. A support added afterwards will passpost_type_supports()checks but won’t trigger any of GatherPress’s internal wiring. - The
Event::POST_TYPEconstant still exists and refers togatherpress_event. It is used for GatherPress’s own post type registration but should not be used for feature checks. - The
Venue::POST_TYPEconstant still exists and refers togatherpress_venue. It is used for GatherPress’s own post type registration but should not be used for feature checks. - The
venuePostTypesmap is exposed to the block editor viablock_editor_settings_allundersettings.gatherpress.venuePostTypes. It maps event post type slugs to their corresponding venue post type slugs, resolved via thegatherpress_venue_post_typefilter. gatherpress-venue-informationimplicitly declaresgatherpress-shadow-sourcevia aregistered_post_typehook onVenue\Setup(priority 9), so any post type that opts into venue support automatically picks up the shadow-taxonomy primitive without having to declare both.