Hooks & Filters

140+ hooks.
No core files to edit.

FotoGrids ships 140+ distinct hook names and 20+ JavaScript events — all of them in Free. One naming rule, three scopes on every render hook, and a public render API that Pro itself is built on top of.

0 +
Distinct hook names
All of them in Free
0 +
JavaScript events
Native CustomEvent, no jQuery
0
Scopes per render hook
Site → type → then one id
0
Core files to edit
Nothing is patched
The map

The three families you reach for first

Every hook is namespaced by area, so the prefix tells you where it fires. Most work happens in three of those areas. A handful exist only as render bases that fan out into three concrete hooks each, and a few are fired as string literals with no constant behind them.
Render pipeline
fotogrids/render/final_html
fotogrids/render/collection_items
fotogrids/render/anchor_attrs
fotogrids/render/wrapper_css_classes
fotogrids/render/wrapper_data_attrs
fotogrids/render/css_variables
fotogrids/render/active_modules
fotogrids/render/layout/style_vars
fotogrids/render/layout/wrapper_attrs
fotogrids/render/register_modules
fotogrids/render/should_inline_assets
fotogrids/render/breakpoint_config
View Pages, content & lifecycle
fotogrids/view/gallery_html
fotogrids/view/before_gallery
fotogrids/view/head_meta
fotogrids/view/og/image
fotogrids/view/body_classes
fotogrids/view/footer_credit
fotogrids/lightbox/slides
fotogrids/actions/item/added
fotogrids/actions/item/meta/updated
fotogrids/actions/gallery/reordered
fotogrids/data/exif/extract
fotogrids/breadcrumb/render_html
Settings, cache & licensing
fotogrids/settings/defaults/gallery
fotogrids/settings/edit_gate
fotogrids/catalog/json_files
fotogrids/catalog/field_state
fotogrids/cache/should_cache
fotogrids/cache/bucket
fotogrids/cache/flushed_for_gallery
fotogrids/features/pro/can_use
fotogrids/features/access_state
fotogrids/permissions/check
fotogrids/templates/can_apply
fotogrids/seo/resolved
One callback, three scopes

Name the scope, skip the branching

Every render hook is fired three times, broad to specific. You pick the scope by choosing the hook name — not by branching inside a global filter.
Hook names
the fan-out
Hook names
				
					fotogrids/render/final_html                       // every collection
fotogrids/render/final_html/{gallery|album}       // one type
fotogrids/render/final_html/{gallery|album}/{id}  // one collection
				
			

The bases live under fotogrids/render/:
the actions before_render and after_render, and the filters render_settings, collection_items, wrapper_css_classes, wrapper_data_attrs, css_variables, active_modules, final_html and anchor_attrs.

Each one fans out into three concrete WordPress hooks, fired in that order. The callback signature is identical at all three, so moving a change from every collection on the site down to one Gallery means editing the hook string and nothing else.

Every collection on the site

PHP
functions.php
PHP
				
					add_filter( 'fotogrids/render/final_html', function ( $html, $render ) {
    return $html . '<p class="credit">© ' . esc_html( get_bloginfo( 'name' ) ) . '</p>';
}, 10, 2 );
				
			

Gallery 42 only

PHP
functions.php
PHP
				
					add_filter( 'fotogrids/render/final_html/gallery/42', function ( $html, $render ) {
    return str_replace( 'fg-grid', 'fg-grid my-custom-class', $html );
}, 10, 2 );

				
			
$html is the complete rendered collection markup. $render is a FotoGrids\Render\Api\Render_Context with public properties ->meta, ->layout, ->behavior, ->settings, ->items, ->warnings and ->via_album_id. Return a non-string and it is discarded — the original HTML is kept.
In practice

Five hooks carry most of the work

PHP

anchor_attrs

PHP

				
					add_filter( 'fotogrids/render/anchor_attrs', function ( array $attrs, $render ) {
    $attrs['data-elementor-open-lightbox'] = 'no';
    $attrs['data-no-lazy']                 = '1';
    return $attrs;
}, 10, 2 );

				
			

fotogrids/render/anchor_attrs

This is the hook that stops a page builder’s global Lightbox hijacking FotoGrids clicks. Args: $attrs (array<string,string> — the attribute map on the <a> wrapping each item: href, target, rel, data-fg-item-id) and $render. Fired from five decorators — external link, Lightbox, direct link, Album to View Page, Album to Gallery AJAX — and cast to array at every call site.

PHP

collection_items

PHP

				
					add_filter( 'fotogrids/render/collection_items', function ( array $items, $render ) {
    return array_slice( $items, 0, 12 );
}, 10, 2 );
				
			

fotogrids/render/collection_items

Args: $collection_items (array<int, FotoGrids\Render\Api\Item_View> the item list after every decorator has run, before layout selection) and $render. The array you return is written straight back into the render context, so it changes what the layout renders, what the Lightbox advertises and the pagination totals. Return an array.

PHP

lightbox/slides

PHP

				
					add_filter( 'fotogrids/lightbox/slides', function ( array $slides, array $ids, array $settings ) {
    foreach ( $slides as $i => $slide ) {
        $slides[ $i ]['price'] = get_post_meta( $ids[ $i ], '_price', true );
    }
    return $slides;
}, 10, 3 );
				
			

fotogrids/lightbox/slides

The only supported way to add fields to Lightbox slides. Whatever you add arrives in the frontend slide object. Args: $slides (resolved slide payloads; per-slide keys include item_type, thumb_url, full_url, video_src, embed_provider, embed_id, embed_settings, and exif when EXIF display is on), $ids (attachment IDs in slide order) and $settings.

PHP

cache

PHP

				
					add_filter( 'fotogrids/cache/should_cache', function ( $should, array $settings, int $gallery_id ) {
    return is_user_logged_in() ? false : $should;
}, 10, 3 );

add_filter( 'fotogrids/cache/bucket', function ( $bucket, array $settings, int $gallery_id ) {
    return is_user_logged_in() ? 'member' : $bucket;
}, 10, 3 );
				
			

fotogrids/cache/should_cache

should_cache takes $should_cache (bool), $settings and $gallery_id. bucket takes $bucket (string, default 'default') plus the same two — and is mixed into the cache key, so per-role or per-user variants get their own entries. Returning true from should_cache cannot re-enable caching in a preview, AJAX or REST context: those bail out before the filter is reached.

PHP

register_modules

PHP

				
					add_action( 'fotogrids/render/register_modules', function () {
    \FotoGrids\Render\Internal\Module_Registry::register(
        'layouts',
        \My\Plugin\Layout_Spiral::class  // implements FotoGrids\Render\Api\Layout
    );
}, 20 );
				
			

fotogrids/render/register_modules

An action with no arguments, and the real extension point. Register a layout, gate, decorator, sorter, filter source, feature or sidecar with Module_Registry::register( string $slot, string $class ). It is the same door Pro comes through. Sibling: fotogrids/render/register_hover_effects, also no arguments.

View Pages — the standalone Gallery template

PHP
view/before_gallery
PHP
				
					add_action( 'fotogrids/view/before_gallery', function ( \WP_Post $post ) {
    echo '<p class="intro">' . esc_html( get_the_excerpt( $post ) ) . '</p>';
} );
				
			
Every action in the View Page template takes one \WP_Post: fotogrids/view/head, before_shell, header, before_gallery, after_gallery, footer, after_shell. The filter fotogrids/view/gallery_html takes $html and $post.

Item save — inside the REST transaction

PHP
save/item/metadata
PHP
				
					add_filter( 'fotogrids/save/item/metadata', function ( array $results, int $item_id,
    \WP_REST_Request $request ) {

    $moods = (array) $request->get_param( 'moods' );
    if ( $moods ) {
        update_post_meta( $item_id, '_fg_moods', $moods );
        $results['moods'] = $moods;
    }
    return $results;
}, 10, 3 );
				
			
Runs inside the item-save REST transaction, so your metadata persists atomically with the core save — and what you return becomes the metadata key of the REST response.
On the front end

Every front-end event is a native CustomEvent

FotoGrids registers no wp.hooks actions or filters of its own, and dispatches no jQuery events. addEventListener is the whole API.
Event
What it carries
On document

fotogrids:ready

The runtime has booted. No detail payload.

fotogrids:gallery_inserted

A Gallery’s markup has landed in the DOM. Detail carries galleryElement, galleryId and kind.

fotogrids:gallery_initialized

The Gallery is wired up and interactive. Same detail, plus instance.
Gallery

fotogrids:share

An item was shared. Detail carries itemId and network.

fotogrids:gallery_unlocked

A password gate was cleared. Detail carries galleryId.

fotogrids:album_swapped

An Album swapped one of its Galleries in over AJAX. Detail carries albumEl and galleryId.

fotogrids:album_restored

The Album view came back. Detail carries albumEl.

fotogrids/filters/ready

The filter UI has finished building. No detail, and this one does not bubble.
Runtime and Gallery events fire on document, so a single listener covers every Gallery on the page.
Event
What it carries
On the Gallery element — these bubble

fotogrids:lightbox:open

The Lightbox opened. Detail carries galleryEl.

fotogrids:lightbox:close

The Lightbox closed. Detail carries galleryEl.

fotogrids:lightbox:navigate

The visitor moved to another slide. Detail carries galleryEl.

These fire on the Gallery element and bubble, so you can listen on document or scope to one render. e.detail.galleryEl tells you which Gallery they came from.

Event
What it carries
On the Gallery element

fotogrids:page_changed

The visitor moved to another page of items.

fotogrids:items_inserted

New items were appended by Load More or endless scroll.

fotogrids:filters_changed

The active filter set changed.
Bind these on the Gallery element when a page carries more than one Gallery. Use items_inserted rather than page_changed when your work has to run against the newly added items.
Event
What it carries
Gallery editor

fotogrids:collection_saved

A Gallery or Album saved successfully.

fotogrids:gallery_save_error

A save failed.

fotogrids:setting_changed

A setting changed in the Gallery metabox or the featured-image picker.

fotogrids:tool-component-registered

A tool registered itself with the tools registry.
Admin modal

fotogrids:admin:modal:opened

A modal opened.

fotogrids:admin:modal:closed

A modal closed.

fotogrids:admin:modal:confirmed

The confirm action in a modal was taken.

fotogrids:admin:modal:tab-changed

The active tab inside a modal changed.
wp-admin only. The modal events share the fotogrids:admin:modal: prefix.
PRO AS A CONSUMER

Pro plugs into Free through the hooks you already have

Step 01

Detect Free

Pro has its own autoloader and finds Free through the FOTOGRIDS_VERSION constant. Nothing else is shared at load time.

Step 02

Answer the licence questions

The fotogrids/features/pro/ filters are where licence answers come from. fotogrids/features/pro/can_use takes $can_use — always false in Free — and $feature_id.

Step 03

Register modules and fields

fotogrids/render/register_modules is how Pro’s own layouts and decorators enter the render pipeline. fotogrids/catalog/json_files puts its settings fields into the admin UI.

Step 04

Build on the public API

Pro consumes FotoGrids\Render\Api\Render_Context, Module_Assets, Asset_Decl and the Layout and Decorator interfaces — the same classes you get.

A handful of hooks are integration seams — the ones a plugin attaches to when it wants to sit inside FotoGrids rather than beside it. Pro comes through these; so can you.

Integration hook
What it is for
fotogrids/features/pro/is_active
Answers whether a licensed build is present
fotogrids/features/pro/can_use
Answers per-feature licence questions
fotogrids/features/pro/on_plan
Answers plan questions
fotogrids/features/pro/enabled
Returns the enabled feature list
fotogrids/admin/page_hooks
Adds an admin screen under the FotoGrids menu
fotogrids/catalog/json_files
Adds settings fields to the Gallery settings UI
fotogrids/render/register_modules

Registers layouts, decorators and other render modules

fotogrids/templates/save_as_template_button
Adds a control to the save-as-template row

Your fields, in the FotoGrids settings UI

fotogrids/catalog/json_files takes $json_file_paths — the absolute paths to the settings-catalog JSON files that build the entire Gallery settings UI. Append a path and your own fields appear alongside the built-in ones.

Return a non-array and the catalog empties; non-string entries are dropped. This is Pro’s actual mechanism, unchanged.

Pro is a third-party plugin as far as Free is concerned. Anything Pro does through these hooks, your plugin can do too.
PHP

catalog/json_files

PHP
				
					add_filter( 'fotogrids/catalog/json_files', function ( array $paths ) {
    $paths[] = plugin_dir_path( __FILE__ ) . 'catalog/my-fields.json';
    return $paths;
} );
				
			

Frequently asked questions

No. Every hook name and every JavaScript event is in Free. The live feature record reads “Developer API & Hooks — Free, Live now”. Free is already a full kit. Pro layers on the rest.

No. The convention is fotogrids/{category}/{subcategory}/{action} and it holds across the board. fg- and data-fg- are CSS class and attribute prefixes, not hooks.

Use the scoped variant. Every render hook fires three times — flat, then /{gallery|album}, then /{gallery|album}/{id} — with an identical callback signature. fotogrids/render/final_html/gallery/42 reaches Gallery 42 and nothing else.

Yes. Hook fotogrids/render/register_modules and call Module_Registry::register('layouts', My_Layout::class) with a class implementing FotoGrids\Render\Api\Layout. Layouts have no filters of their own — registering a module is how you extend them.

Every hook is declared as a constant on a hook-bag class under Plugin/src/includes/hooks/{actions,filters,js-events}/, and each one carries a docblock and an @since tag, so autocomplete resolves the constant and the docblock tells you the signature.

No. There are no wp.hooks actions or filters of its own and no jQuery events. Every front-end event is a native CustomEvent, so addEventListener is the whole API. Lightbox, pagination and filter events fire on the Gallery element and bubble; the runtime events fire on document.