JMGallery is built to be extended. Pro is not privileged code: it is an add-on built entirely on the same public hooks you have.

The contract

The full reference ships in the plugin at jmarztech-gallery/docs/hooks.md. It is the authority; this page is the orientation.

Plugins load alphabetically, so an add-on in a folder sorting before jmarztech-gallery/ loads first. Do all your setup on jmzg_loaded, which fires on plugins_loaded once the gallery is ready.

add_action( 'jmzg_loaded', function () {
    // register field types, display keys, settings, REST routes
} );

Naming

Post typejmzg_media
Meta and taxonomy keysjmzg_ + the field key
Optionsjmzg_settings, jmzg_fields, jmzg_displays
REST namespacejmarztech/v1
CSS namespace.jmz-
Classes and functionsjmzg_ and jmzg_

No custom database tables. Items are posts, values are post meta or terms.

The main extension points

Fields

jmzg_field_types registers a type with its storage, whether it holds many values, and whether it is sortable, filterable and caption-able. jmzg_sanitize_field, jmzg_field_value and jmzg_display_field handle writing, reading and formatting. jmzg_meta_query_clause defines how it filters.

A field keeps its stored type and storage even when no loaded code registers that type. It then behaves as text, or as tags if taxonomy-stored, and saving never rewrites it. Build on that: your add-on going missing should not cost the site its data.

Displays

jmzg_display_defaults adds your keys, jmzg_display_sanitize cleans them, and jmzg_display_style_vars, jmzg_display_classes, jmzg_display_attributes, jmzg_display_before and jmzg_display_top put them into the output. Display keys the plugin does not own are never dropped when a display is saved.

Settings

jmzg_settings_defaults and jmzg_settings_sanitize. Keys you own are carried through untouched when settings are saved from a build that cannot see them.

Output

jmzg_card_caption rebuilds one card’s caption from its parts. jmzg_card_prefix and jmzg_single_actions insert markup. jmzg_image_alt controls alt text. Everything returned must already be escaped.

REST

jmzg_rest_routes adds routes under jmarztech/v1. Each entry is [ path, method, callable, optional permission callable ]; the default permission is edit_posts.

The admin app

The admin is a single script, app.js. Enqueue on jmzg_app_enqueue with 'jmz-app' as a dependency, then register through window.jmzApp:

jmzApp.types.mytype = { input(f, v, name, extra) {}, parse(f, el) {}, fmt(f, v) {} };
jmzApp.formatters.push((f, v) => f.key === 'price' ? '$' + v : undefined);
jmzApp.add('display.layout',     (d, H) => '<label>…</label>');
jmzApp.wire('display.layout',    (scope, d, H) => {});
jmzApp.collect('display.layout', (scope, obj, H) => { obj.key = …; });

Slots: display.layout, display.captions, display.sections, display.listitem, csv.import, settings.sections, upload.video. When no add-on fills a slot, the free plugin renders a disabled preview of what an add-on would put there. Switch those off with jmzg_show_upsell.

Templates

Copy templates/archive-photo.php or templates/single-photo.php into your theme to override them. Block themes get both as block templates, plus

100 items
Rolling Green Hills and Trees
File Name: Rolling Green Hills and TreesInfo Nature / March 1, 2026 / Illustrated
Misty Mountain Lake
File Name: Misty Mountain LakeInfo Nature / March 1, 2026 / Illustrated
Alpine Peaks Pine Forest
File Name: Alpine Peaks Pine ForestInfo Nature / March 1, 2026 / Illustrated
Mossy Waterfalls
File Name: Mossy WaterfallsInfo Nature / March 1, 2026 / Illustrated
and for custom ones.

Translation

Both plugins are fully translatable and ship a POT file in languages/. Text domains are jmarztech-gallery and jmarztech-gallery-pro.