Hook naming convention

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_providers
    • gatherpress_static_map_prewarm_run
    • gatherpress_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:

  1. 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.
  2. @since x.y.z with the version the hook was introduced.
  3. @param lines for every argument.
  4. @return for 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

  1. Identify the subsystem. If none fits, add one to this doc.
  2. Pick the depth: flat, one segment, or two. Default to one.
  3. Pick filter vs action — filters return a value, actions fire and return
    nothing.
  4. Pick the verb tense (actions only): imperative for callable, past for
    outcome.
  5. Write the docblock. Explain the why, not the what.
  6. Run vendor/bin/extract-wp-hooks.php locally 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.x release), rename
    in place. No deprecation shim needed.