Post type supports

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’s custom-fields support to the post type so the REST controller actually attaches the meta field to the schema (without it, register_post_meta() quietly registers the keys but the editor’s PUT silently strips them)
  • Storage in the gatherpress_events database 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_type action โ€” i.e. at the moment your post type finishes registering. Calling add_post_type_support( 'my_custom_event', 'gatherpress-event-date' ) after register_post_type() will make post_type_supports() return true, but GatherPress’s internal wiring won’t run for your post type. Always include the support in the supports array.

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:

  1. URL parameters: appending ?gatherpress_event_query=upcoming (or past) narrows that page load to the matching subset.
  2. The gatherpress_event_archive_mode filter 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 are upcoming, past, or none โ€” anything else is coerced back to upcoming. Returning none opts 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-event term in the _gatherpress_venue taxonomy

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_rest and bindable via core/post-meta block bindings:
    • gatherpress_address
    • gatherpress_latitude
    • gatherpress_longitude
    • gatherpress_phone
    • gatherpress_website
  • Registration of eight server-populated structured-address meta keys, each show_in_rest for read access (REST writes are stripped, since these are derived from gatherpress_address by an async geocode cron handler that fires only when the address actually changes):
    • gatherpress_house_number
    • gatherpress_street
    • gatherpress_city
    • gatherpress_county
    • gatherpress_state
    • gatherpress_postcode
    • gatherpress_country
    • gatherpress_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 with show_ui => false, show_admin_column => true, publicly_queryable => true, show_in_rest => true, and rewrite => 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 the gatherpress_shadow_taxonomy_args filter)
  • A save_post_<post_type> hook that inserts a term on first publish, with the term slug derived from the post’s post_name prefixed with an underscore (e.g. my-production โ†’ _my-production)
  • A post_updated hook 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_events database table stores data by post_id and is post-type agnostic. Any post type with gatherpress-event-date support can store datetime data in this table.
  • Declare supports in your register_post_type() call, not via a later add_post_type_support(). GatherPress registers its per-post-type hooks (meta fields, admin-list columns, REST query filters, venue save hooks) on the registered_post_type action, which only fires when register_post_type() runs. A support added afterwards will pass post_type_supports() checks but won’t trigger any of GatherPress’s internal wiring.
  • The Event::POST_TYPE constant still exists and refers to gatherpress_event. It is used for GatherPress’s own post type registration but should not be used for feature checks.
  • The Venue::POST_TYPE constant still exists and refers to gatherpress_venue. It is used for GatherPress’s own post type registration but should not be used for feature checks.
  • The venuePostTypes map is exposed to the block editor via block_editor_settings_all under settings.gatherpress.venuePostTypes. It maps event post type slugs to their corresponding venue post type slugs, resolved via the gatherpress_venue_post_type filter.
  • gatherpress-venue-information implicitly declares gatherpress-shadow-source via a registered_post_type hook on Venue\Setup (priority 9), so any post type that opts into venue support automatically picks up the shadow-taxonomy primitive without having to declare both.