Last updated July 11, 2026, Version 0.34
This document defines the naming rules for every public filter (apply_filters())
and action (do_action()) GatherPress exposes to third-party developers. It is
the source of truth that the per-hook reference at
docs/developer/hooks/ is generated against.
The per-hook pages in that folder are auto-regenerated by CI from PHP docblocks — this file is hand-maintained and describes the conventions those docblocks must follow.
Prefix structure
Every public hook starts with the gatherpress_ namespace. After the namespace,
hooks fall into one of three depths:
| Shape | When to use | Example |
|---|---|---|
gatherpress_<thing> |
Plugin-wide concerns with no clear subsystem owner. | gatherpress_roles, gatherpress_timezone, gatherpress_autoloader |
gatherpress_<subsystem>_<thing> |
Anything scoped to a single subsystem. This is the default. | gatherpress_event_feed_excerpt, gatherpress_venue_post_type, gatherpress_async_geocode_delay |
gatherpress_<subsystem>_<area>_<thing> |
Use only when one extra level of disambiguation is needed (e.g. interactive vs static map, scan vs content batch). | gatherpress_static_map_prewarm_batch_size, gatherpress_interactive_map_tile_url |
Avoid going deeper than three segments after gatherpress_. If a hook needs
four levels to make sense, the names are usually wrong, not the depth.
Subsystems
The subsystem segment names a single area of the plugin. The current set:
| Subsystem | Covers |
|---|---|
asset_* |
Stylesheet/script registration, enqueue gating, and block-asset coverage shared with companion plugins. |
event_* |
Event lifecycle, queries, feed output. |
venue_* |
Venue post type resolution, structured-address sync. |
rsvp_* |
RSVP storage (comment-based), attendee management, comment-query coexistence. |
calendar_* |
iCal / Outlook / Google / Yahoo URL builders, .ics file & feed responses, <head> alternate-link tags. |
geocode_* / async_geocode_* |
Photon geocoding (sync filters use geocode_, async/cron lifecycle uses async_geocode_). |
map_* |
Map config that applies to both interactive and static rendering (zoom, height, provider registry). |
interactive_map_* |
Leaflet client-side basemap only (tile URL, attribution). |
static_map_* |
Server-side OSM tile compositor and PNG output only. |
static_map_prewarm_* |
Prewarm cron family (batch sizes, run/sweep actions, enqueue short-circuit). |
settings_* |
Admin settings UI surfaces. |
shadow_* |
Shadow taxonomies (_<post_type>) auto-registered for gatherpress-shadow-source CPTs and the wiring that attaches them to event-bearing CPTs. |
template_* |
Theme-override-aware template path resolution shared across subsystems (see Utility::locate_template()). |
When adding a hook in a subsystem that’s not on this list, add the subsystem here in the same PR.
Interactive vs static maps — why three buckets
GatherPress renders maps two ways:
- Interactive — Leaflet loads tiles client-side. Configured by Settings.
- Static — Server compositions OSM tiles into a PNG via the provider
registry, with a prewarm cron warming variants ahead of time.
Some hooks affect only one pipeline; others affect both. The three-bucket split matches that reality:
gatherpress_map_*— touches both. A site owner hooking it changes behavior
in both pipelines.gatherpress_interactive_map_*— Leaflet-only. Hooking it does nothing to the
static pipeline.gatherpress_static_map_*— server-side only. Hooking it does nothing to the
Leaflet basemap.
When a single concept genuinely applies to both pipelines (zoom, height), use
map_*. When it doesn’t, pick the right one of the other two — don’t pick the
shared bucket out of caution, because it implies the hook will fire from both
pipelines and that becomes a contract.
Word order
- Filters: subsystem first, attribute last.
- ✅
gatherpress_static_map_tile_url - ❌
gatherpress_tile_url_static_map
- ✅
- Actions: subsystem first, verb last.
- ✅
gatherpress_static_map_prewarm_run - ❌
gatherpress_run_static_map_prewarm
- ✅
Verb form (actions)
- Imperative / present tense for callable lifecycle moments — registration
hooks, cron run handlers, render-time fanout.gatherpress_register_static_map_providersgatherpress_static_map_prewarm_rungatherpress_settings_section
- Past tense for outcomes / observability — something already happened, the
hook is for downstream observers.gatherpress_async_geocode_failed
Do not mix tenses in the same family. If you add a _run action, its failure
counterpart should be _failed (not _failure or _failing).
Underscores
Always separate words with underscores. Run-together compound words are not allowed.
- ✅
gatherpress_pseudo_post_metas - ❌
gatherpress_pseudopostmetas
JavaScript hooks
GatherPress exposes JS hooks via @wordpress/hooks (applyFilters,
doAction). These follow WordPress core’s own JS hook convention, not the
PHP snake_case style:
namespace.camelCaseName— dot separates namespace from hook name; the
hook name itself uses camelCase.- The namespace is always
gatherpress(no subsystem segment — JS hooks are
rare enough that flat naming is fine).
Examples:
- ✅
gatherpress.durationOptions - ❌
gatherpress_duration_options(PHP style — wrong for JS) - ❌
gatherpress.duration_options(snake_case in JS hook — wrong)
Rationale: WP core itself uses dot-case + camelCase for its JS hooks
(editor.BlockEdit, blocks.registerBlockType, editor.PostFeaturedImage.imageSize).
Matching that convention means companion plugins do not have to context-switch
between PHP and JS hook styles.
When applyFilters is called from JS, the hook still needs a JSDoc block
above it with @since and a description of what the hook is FOR. The
auto-regenerated reference at docs/developer/hooks/ covers PHP
hooks only today — JS hooks should be documented inline until that
extraction is automated.
Docblock requirements
Every public hook must have a docblock immediately above the apply_filters()
or do_action() call with:
- A one-line description that explains what the hook is FOR, not what it
filters or fires on. The reader should understand why they would hook it.
– ✅Filters the per-user Photon requests-per-minute ceiling. Lower the
ceiling on shared hosting where Photon may flag aggressive callers.
– ❌Filters the rate limit. @since x.y.zwith the version the hook was introduced.@paramlines for every argument.@returnfor filters.
The auto-regen at vendor/bin/extract-wp-hooks.php reads these docblocks
verbatim — gaps here become gaps in the published hook reference.
Adding a new hook
- Identify the subsystem. If none fits, add one to this doc.
- Pick the depth: flat, one segment, or two. Default to one.
- Pick filter vs action — filters return a value, actions fire and return
nothing. - Pick the verb tense (actions only): imperative for callable, past for
outcome. - Write the docblock. Explain the why, not the what.
- Run
vendor/bin/extract-wp-hooks.phplocally to confirm the doc generates
cleanly. Revert the regen output before committing — CI regenerates it on
merge (see AGENTS.md).
Renaming an existing hook
Hook renames are breaking changes for any companion plugin or theme that hooks them. Ship them in coordinated passes, not chipped away one-by-one:
- For hooks already shipped to stable, add
apply_filters_deprecated()/
do_action_deprecated()aliases that forward to the new name and emit a
deprecation notice. - For hooks still in alpha (anything not yet in a stable
0.xrelease), rename
in place. No deprecation shim needed.