# Wisp Gallery

> Wisp Gallery is a free, GPL-licensed WordPress plugin for filterable photo galleries in the block editor: category filters, five layouts (masonry, justified rows, grid, carousel, accordion), scroll reveal effects and a native lightbox. It is built on core blocks and the Interactivity API, loads about 3 KB of JavaScript to start and has no dependencies (no jQuery).

Wisp Gallery is for photographers, artists, illustrators, studios, agencies and businesses (restaurants, real estate, florists, event organizers) that want a portfolio or photo gallery visitors can filter by category, built in the WordPress block editor without a page builder. It is an alternative to gallery plugins such as Envira Gallery, FooGallery, Modula and NextGEN Gallery, and imports their galleries.

Key facts:

- Name: Wisp Gallery. On WordPress.org: https://wordpress.org/plugins/wisp-gallery/ (slug `wisp-gallery`). Website: https://wispgallery.com/
- Author: eedee (https://eedee.net).
- Price: free (GPL-2.0-or-later), no license key or account, on any number of sites. Wisp Gallery Pro is a separate add-on: from $2.99 a month; yearly $29 (1 site), $49 (5 sites) or $79 (unlimited sites); monthly $2.99, $4.99 or $7.99; lifetime (paid once) $99, $149 or $199. Galleries keep working when a Pro license ends.
- Requirements: WordPress 6.6 or newer, PHP 7.4 or newer. Current version: 1.0.1.
- Blocks: Filterable Gallery (the container), Gallery Filter (buttons or dropdown), Gallery Media Query (Media Library images by category), and a "Gallery Posts" Query Loop variation.
- Layouts: masonry (shortest column first), justified rows, grid (cropped to a ratio or height), carousel (arrows, endless loop, centered slide, autoplay; Pro adds autoplay settings, arrow design and a mouse-following cursor), accordion (opens to the photo's own shape).
- Scroll reveal: rise and fade, staggered, pure CSS scroll-driven animations. Pro adds zoom, blur, tilt, wipe, pop, flip, slide, iris and the stagger setting.
- Lightbox: native <dialog>, zooms from the thumbnail, captions and counter. Pro adds light and frosted themes, colors, download and share buttons, camera settings and deep links.
- Videos (Pro): Media Library files, YouTube, Vimeo, TikTok and Instagram next to photos. Paste a link into a gallery to add one. Hover plays a muted preview, a click plays it in the lightbox; until then only the poster image loads.
- Pro also adds WooCommerce product galleries, albums (galleries inside a gallery), load more, filter styles, design controls and priority support.
- Performance: ~3 KB JavaScript and ~2.3 KB CSS to start, only on pages with a gallery; other parts load only where used. Lazy images in the right size; no layout shift.
- WordPress integration: importers for Envira Gallery, NextGEN Gallery, Modula and FooGallery (Tools → Wisp Gallery import, or `wp wisp-gallery import`); Media Library column, filter and bulk actions for Gallery Categories; command palette commands; seven block patterns; a Gallery Category archive template; Abilities API abilities for AI agents (WordPress 6.9+).
- Translations: German, Spanish, French, Portuguese (Brazil), Italian, Japanese and Dutch.
- Works without JavaScript (server-rendered), in block themes and classic themes.

## How it compares

Checked on 25 September 2026 in the free versions on WordPress.org (Envira Gallery Lite 1.16.0, Modula 3.0.9, FooGallery 3.3.3, Visual Portfolio 3.8.2), see https://wispgallery.com/#compare:

- Filter buttons: free in Wisp Gallery and Visual Portfolio; paid in Envira Gallery, Modula and FooGallery.
- JavaScript for a default gallery with a lightbox (gzipped): Wisp Gallery ~8 KB without jQuery; Envira ~29 KB, FooGallery ~57 KB, Modula ~72 KB and Visual Portfolio ~75 KB, each plus jQuery.
- Editor: Wisp Gallery is a native block edited in place; Envira and Modula blocks pick a gallery saved elsewhere.
- Importers: Wisp Gallery imports Envira, NextGEN, Modula and FooGallery galleries for free.
- Price of the full version for one site: Wisp Gallery Pro $29 a year, same at renewal ($2.99 a month).

## Pages

- [Home](https://wispgallery.com/): Free WordPress gallery plugin with category filters, masonry, justified rows, carousel, lightbox and scroll reveal. Core blocks, 3 KB of JavaScript, no jQuery.
- [Layouts](https://wispgallery.com/layouts/): Five WordPress gallery layouts in one block – masonry, justified rows, grid, carousel and accordion – plus scroll reveal effects. Try every option live.
- [100 Photos](https://wispgallery.com/100-photos/): A Wisp Gallery masonry gallery with 100 photos, a category filter and a lightbox: the first row loads at once, the rest lazily in the size it is shown at.
- [Playground](https://wispgallery.com/playground/): Try Wisp Gallery in your browser: switch layouts, columns, captions, hover effects, filter styles and the lightbox on a live gallery. No install needed.
- [Wisp Gallery Pro](https://wispgallery.com/pro/): Wisp Gallery is free on every site. Pro adds design controls, videos, albums, load more and support: from $2.99 a month or $29 a year.
- [Download the plugin](https://wispgallery.com/wp-content/uploads/wisp-gallery/wisp-gallery.zip): zip, install via Plugins → Add New → Upload.

## Docs

- [Docs](https://wispgallery.com/docs/): Wisp Gallery docs: getting started, every block option, the five layouts, categories, importing from Envira, NextGEN, Modula and FooGallery, and AI abilities.
- [Getting started](https://wispgallery.com/docs/getting-started/): Install Wisp Gallery, add your first filterable gallery with the “Add filterable gallery” command, assign categories and connect the filter. Step by step. Markdown: https://wispgallery.com/docs/getting-started.md
- [Blocks & options](https://wispgallery.com/docs/blocks/): Reference for the Wisp Gallery blocks – Filterable Gallery, Gallery Filter, Gallery Media Query and Gallery Posts – with every option, value and default. Markdown: https://wispgallery.com/docs/blocks.md
- [Layouts](https://wispgallery.com/docs/layouts/): The five Wisp Gallery layouts – masonry, justified rows, grid, carousel and accordion – how each one works, its options, and scroll reveal effects. Markdown: https://wispgallery.com/docs/layouts.md
- [Editing](https://wispgallery.com/docs/editing/): Working with Wisp Gallery in the block editor: Edit and Preview modes, filter preview, bulk categories, sorting, block transforms and command palette commands. Markdown: https://wispgallery.com/docs/editing.md
- [Categories & Media Library](https://wispgallery.com/docs/categories/): How Gallery Categories work in Wisp Gallery: one taxonomy for images, posts and pages, plus a Media Library column, filters and bulk actions. Markdown: https://wispgallery.com/docs/categories.md
- [Switching from other plugins](https://wispgallery.com/docs/switching/): Import Envira, NextGEN, Modula and FooGallery galleries into Wisp Gallery: what carries over, the WP-CLI command and the replacement shortcodes. Markdown: https://wispgallery.com/docs/switching.md
- [Patterns & archive template](https://wispgallery.com/docs/patterns/): The Wisp Gallery block patterns (portfolio, travel journal, food menu, team, carousels) and the Gallery Category Archive template for block themes. Markdown: https://wispgallery.com/docs/patterns.md
- [AI & Abilities API](https://wispgallery.com/docs/abilities/): Four WordPress Abilities API abilities for AI agents and MCP clients: list categories, find images, tag images and create gallery pages, with REST methods. Markdown: https://wispgallery.com/docs/abilities.md
- [Developers](https://wispgallery.com/docs/developers/): Developer notes for Wisp Gallery: WP-CLI commands, the Playground blueprint, filters and hooks, stylesheet and script handles, and how it stays fast. Markdown: https://wispgallery.com/docs/developers.md

## Blog

- [Blog](https://wispgallery.com/blog/): Practical articles about Wisp Gallery: custom styles, switching from other gallery plugins, performance without jQuery, filterable portfolios and AI agents.
- [Custom styles for Wisp Gallery](https://wispgallery.com/blog/custom-styles/): Style Wisp Gallery from your theme: filter styles with register_block_style, caption looks via CSS variables, a custom hover effect and theme.json. Markdown: https://wispgallery.com/blog/custom-styles.md
- [Switching from Envira, NextGEN, Modula or FooGallery](https://wispgallery.com/blog/switching-gallery-plugins/): Move Envira, NextGEN, Modula and FooGallery galleries to Wisp Gallery: what the importer carries over, replacement shortcodes, WP-CLI and costs. Markdown: https://wispgallery.com/blog/switching-gallery-plugins.md
- [Why a gallery doesn’t need jQuery in 2026](https://wispgallery.com/blog/gallery-without-jquery/): Native dialog lightbox, scroll-snap carousels, CSS masonry and scroll-driven reveals: how a WordPress gallery starts at 3 KB of JavaScript. Markdown: https://wispgallery.com/blog/gallery-without-jquery.md
- [Filterable portfolios with posts and images in one gallery](https://wispgallery.com/blog/filterable-portfolio/): Tutorial: a filterable WordPress portfolio with project posts, images and a Media Library query in one gallery, one category filter for all. Markdown: https://wispgallery.com/blog/filterable-portfolio.md
- [Letting AI agents build galleries: the Abilities API](https://wispgallery.com/blog/ai-abilities/): Wisp Gallery’s four WordPress abilities let AI agents list categories, find and tag images and create gallery pages. REST routes, permissions, uses. Markdown: https://wispgallery.com/blog/ai-abilities.md

## Demos

- [Demos](https://wispgallery.com/demos/): Live WordPress gallery demos made with Wisp Gallery: artist, illustration, wedding, restaurant and real estate galleries, and TikTok, Instagram and Shorts.
- [TikTok gallery](https://wispgallery.com/demos/tiktok-gallery/): Show TikTok videos in a WordPress gallery with Wisp Gallery Pro: paste links, get a 9:16 grid with hover previews and a lightbox player. No API key.
- [Instagram gallery](https://wispgallery.com/demos/instagram-gallery/): Hand-pick Instagram reels and posts for a WordPress gallery with Wisp Gallery Pro: paste links, open them in a lightbox. No feed sync, no access token.
- [YouTube Shorts gallery](https://wispgallery.com/demos/youtube-shorts-gallery/): YouTube Shorts in a WordPress gallery with Wisp Gallery Pro: vertical posters, muted previews on hover, playback in a lightbox from youtube-nocookie.com.
- [Artist portfolio](https://wispgallery.com/demos/artist-portfolio/): A WordPress portfolio gallery for painters and artists: justified rows that never crop a canvas, a filter by subject and a lightbox. Live demo.
- [Illustration portfolio](https://wispgallery.com/demos/illustration-portfolio/): A masonry portfolio for illustrators, studios and agencies: one filter tab per artist, captions on hover and a lightbox. Live demo with classic prints.
- [Wedding photography](https://wispgallery.com/demos/wedding-photography-gallery/): A wedding gallery for photographers: ceremony, portraits, details and party as filter chapters, masonry or justified rows, a swipeable lightbox.
- [Real estate listing](https://wispgallery.com/demos/real-estate-gallery/): A property gallery for real estate listings: a carousel of the house filtered by room, arrows on the photos and swipe on phones. Live demo.
- [Restaurant menu](https://wispgallery.com/demos/restaurant-menu-gallery/): A menu gallery for restaurants and cafés: dishes in a square grid with names, a filter for starters, mains, desserts and drinks. Live demo.
- [Tattoo portfolio](https://wispgallery.com/demos/tattoo-portfolio/): A portfolio for tattoo studios: healed work in a 4:5 grid, filtered by style or artist with counts, plus Instagram posts if you like. Live demo.
- [Fashion lookbook](https://wispgallery.com/demos/fashion-lookbook/): A lookbook for fashion brands and stylists: an accordion gallery that opens each look to its own shape on hover, with names on the photos.
- [Florist gallery](https://wispgallery.com/demos/florist-gallery/): A gallery for florists: bouquets, wedding flowers, workshops and the shop in masonry with a category filter, plus workshop videos. Live demo.
- [Load more](https://wispgallery.com/demos/load-more-gallery/): A 72-photo WordPress gallery that shows twelve photos first and twelve more per click on Load more, or on scroll. Only what shows downloads.
- [Event photos](https://wispgallery.com/demos/event-photo-gallery/): An event and conference photo gallery: talks, workshops, networking and audience in justified rows with a filter, fast with hundreds of photos.
- [Ceramics portfolio](https://wispgallery.com/demos/ceramics-portfolio/): A portfolio gallery for ceramicists and makers: mugs, bowls and vases in a square grid with captions, filtered by type, linked to your shop.

## FAQ

### What is Wisp Gallery?

Wisp Gallery is a free WordPress plugin for filterable photo galleries in the block editor, by eedee. One block gives you five layouts (masonry, justified rows, grid, carousel and accordion), category filter buttons, scroll reveal effects and a native lightbox, with about 3 KB of JavaScript to start and no jQuery. It is made for photographers, artists, studios, agencies and businesses that want a portfolio or gallery visitors can filter, without a page builder. See the demos.

### Is it an alternative to Envira, FooGallery, Modula or NextGEN?

Yes. Filter buttons are free in Wisp Gallery; in the free versions of Envira Gallery, Modula and FooGallery they are paid features. For a default gallery with a lightbox Wisp Gallery loads about 8 KB of JavaScript without jQuery, where those plugins load 29 to 72 KB plus jQuery (checked in September 2026). It also imports their galleries and NextGEN’s. See the comparison and Switching from other plugins.

### Can I filter a WordPress gallery by category?

Yes, that is what Wisp Gallery is for. Put a Gallery Filter block next to a Filterable Gallery and visitors filter the photos by the categories you assigned, with an animated transition. With Pro, the selection can be shared in the URL, like ?category=nature. Step by step: Getting started.

### How is it different from the core Gallery block?

The core Gallery block shows one grid. Wisp Gallery adds category filters, five layouts (masonry, justified rows, grid, carousel and accordion), scroll reveal effects, a lightbox that pages through the visible photos, and posts or Media Library queries as gallery items. Inside, the photos stay regular Image blocks. See Blocks & options.

### Will it slow down my site?

No. A page with a gallery loads about 3 KB of JavaScript to start and 2.3 KB of CSS; carousel, accordion, lightbox and effect code loads only where a gallery uses it, and nothing loads on pages without a gallery. Images lazy-load in the right size, and masonry is placed before the first paint, so nothing shifts. The details are in the performance notes.

### Does it work with my theme?

Yes. Wisp Gallery uses block supports and your theme’s own styles: filter buttons start from the theme’s button style, captions from its caption style, spacing from its gap. It works in block themes and in classic themes that use the block editor.

### How do the categories work?

Wisp Gallery adds a Gallery Categories taxonomy that media, posts and pages share. Assign categories in the image sidebar inside a gallery or in the Media Library. Many images at once? Use the bulk actions in the Media Library. With Pro, a gallery can also filter by any other taxonomy, such as regular post categories. More in Categories & Media Library.

### Can I switch from Envira, NextGEN, Modula or FooGallery?

Yes. Under Tools → Wisp Gallery import (or with wp wisp-gallery import), each gallery becomes a draft page with a Filterable Gallery: images, captions, alt text, links and columns come along, and tags become filter categories. Lightbox themes, spacing and hover effects are not carried over. See Switching from other plugins.

### Can I show posts instead of images?

Yes. Put a Query Loop inside the gallery – there is a “Gallery Posts” variation – and the featured images become gallery items with their titles as captions. Images, posts and Media Library queries can be mixed in one gallery. See Gallery Posts.

### What happens without JavaScript?

Every item shows, rows and grids keep their layout (masonry becomes a plain column grid), and links still work. Lightbox links point to the full image file. See How it works.

### Which browsers are supported?

All current browsers. View Transitions, @starting-style, color-mix(), scroll-driven animations and backdrop-filter are progressive enhancements: older browsers get the same features with simpler animations.

### What does it need?

WordPress 6.6 or newer and PHP 7.4 or newer. No build step, no API keys, no external services.

### Which languages is it available in?

Wisp Gallery ships translated into German, Spanish, French, Portuguese (Brazil), Italian, Japanese and Dutch, covering every string of the free plugin and Pro. WordPress picks the language of your site automatically. More languages follow, and anyone can help translate it on WordPress.org.

### Is it really free?

Yes. The free plugin is licensed under the GPL v2 or later and has everything to build and filter galleries: all five layouts, the filter block, the lightbox and the importers. Pro adds more styling, videos, WooCommerce products and more. A gallery made with Pro keeps working when a license ends. See Wisp Gallery Pro.

### Is the free version really complete?

Yes. Layouts, the filter, the lightbox, scroll reveal, the importers and the editor tools are free and stay free, on as many sites as you like. Pro adds finer design controls, video galleries, albums, load more, WooCommerce product galleries, lightbox downloads and deep links, premium patterns and priority support on top.

### What happens when my Pro license ends?

Your galleries keep looking exactly the same. Pro settings stay in place and render as before; you just can’t change them, and updates and priority support stop until you renew.

### Monthly or yearly?

Monthly is there if you only need Pro for a project. Yearly costs the same as ten months, and renewals never go up.

### Can I move to a bigger plan later?

Yes. Upgrade from Personal to Pro or Agency at any time and only pay the difference.

### Can I use Pro on client sites?

Yes. Pro covers five sites and Agency unlimited ones, including sites you build for clients.

### How do I pay?

By card or PayPal, through our reseller Freemius. They handle the invoice and the VAT for your country.

### What license is Pro under?

Free and Pro are both GPL v2 or later, like WordPress itself. Your Pro license key unlocks the Pro settings in the editor, updates and priority support for the sites your plan covers.

## Documentation

### Getting started

> Install Wisp Gallery, add your first filterable gallery with the “Add filterable gallery” command, assign categories and connect the filter. Step by step.

Source: https://wispgallery.com/docs/getting-started/

From a fresh install to a published, filterable gallery in about five minutes. You need WordPress 6.6 or newer and PHP 7.4 or newer – nothing else, no account and no API key.

#### Install the plugin

1. Download `wisp-gallery.zip` from this site.
2. In WordPress, go to **Plugins → Add New → Upload Plugin**, choose the zip and click **Install Now**.
3. Click **Activate**.

With WP-CLI it is one line:

```
wp plugin install wisp-gallery.zip --activate
```

Wisp Gallery works in block themes and in classic themes that use the block editor. There is no settings page: everything is a block setting.

#### Requirements

| Needs | Version | For |
| --- | --- | --- |
| WordPress | 6.6+ | The blocks, filters, lightbox and everything on this page |
| WordPress | 6.7+ | The Gallery Category Archive template in block themes |
| WordPress | 6.9+ | The Abilities API for AI agents (skipped on older versions) |
| PHP | 7.4+ | Everything |

#### Your first filterable gallery

A filterable gallery is two blocks that know each other: a **Gallery Filter** (the category buttons) and a **Filterable Gallery** (the photos). The quickest way to get both, already connected:

1. Open a page in the editor and press `Cmd`+`K` (`Ctrl`+`K` on Windows and Linux) to open the command palette.
2. Type *gallery* and choose **Add filterable gallery**. A filter and a gallery appear at the cursor, wired to each other.
3. In the empty gallery, click **Add images** and select photos from the Media Library (shift-click to select several).

You can also insert the two blocks from the inserter yourself. Then give the gallery a **Gallery ID** (Filtering panel) and pick the same gallery in the filter’s **Gallery** setting – or leave the filter set to “all galleries on the page”.

#### Give the photos categories

The filter shows one button per **Gallery Category** used by the photos in the gallery. Categories are stored on the images themselves, in the Media Library, so they follow a photo into every gallery.

- **One image:** select it and use the **Gallery categories** panel in the sidebar.
- **Several images:** select one, shift-click the others, then click the tag button in the block toolbar. Tick categories or create new ones right there.
- **Many images at once:** use the bulk actions in the Media Library’s list view.

Click **Edit** in the gallery’s toolbar to see every photo with a badge of its categories. Photos without one get a yellow “No category” badge, so you see at a glance what still needs tagging.

#### Try the filter

Switch the gallery back to **Preview** in its toolbar and click the filter buttons in the editor: the gallery filters right there, so you can check the categories before you publish. Nothing is saved by clicking.

Then publish and click a button on the front end. Photos move to their new places with an animated transition. Want a shareable link per category? With Wisp Gallery Pro, set a **URL parameter** in the filter’s settings, e.g. `category`, and `?category=food` opens the page already filtered.

#### Where to go next

- Pick a [layout](https://wispgallery.com/docs/layouts/): masonry is the default; justified rows, grid, carousel and accordion are one click away.
- Turn on the **lightbox**: set *Click behavior* to *Lightbox* in the Items panel.
- Style the filter with your theme’s colors and type in the block settings, or with a preset and more styles in Wisp Gallery Pro – see [Gallery Filter](https://wispgallery.com/docs/blocks/#filter).
- Coming from another gallery plugin? [Import your galleries.](https://wispgallery.com/docs/switching/)

Further reading: [a filterable portfolio with posts and images in one gallery](https://wispgallery.com/blog/filterable-portfolio/), step by step, and [live demos](https://wispgallery.com/demos/) for portfolios, weddings, restaurants and more.

### Blocks & options

> Reference for the Wisp Gallery blocks – Filterable Gallery, Gallery Filter, Gallery Media Query and Gallery Posts – with every option, value and default.

Source: https://wispgallery.com/docs/blocks/

Wisp Gallery adds three blocks and one Query Loop variation. The photos inside a gallery stay ordinary core blocks, so everything core offers for Image, Query Loop and Featured Image keeps working.

> Options marked **(Pro)** are set with Wisp Gallery Pro. The free plugin renders all of them, so a gallery keeps its look when a license ends. The [Pro features](#pro) are listed at the end.

| Block | What it does |
| --- | --- |
| **Filterable Gallery** 
`wisp-gallery/gallery` | The container. Lays out its content in one of five layouts, with sizes per device, captions, hover effects, a lightbox and filter animations. |
| **Gallery Filter** 
`wisp-gallery/filter` | Buttons or a dropdown that filter one gallery, or every gallery on the page. Place it anywhere – above, below or beside the gallery. |
| **Gallery Media Query** 
`wisp-gallery/media-query` | Media Library images by Gallery Category, inside a gallery. New uploads show up without editing the page. |
| **Gallery Posts** 
`core/query` variation | Posts as gallery items, built from the regular Query Loop, Post Template and Featured Image blocks. |

#### Images, posts and media in one gallery

A gallery can hold Image blocks, a Gallery Posts query and a Gallery Media Query side by side. On the front end the groups dissolve into one shared grid (`display: contents`), so one filter and one lightbox cover all of them. Each post item links to its page; post titles and excerpts use the same caption styles as image captions.

#### Filterable Gallery

Sizes marked “per device” have a desktop, tablet (under 782px) and mobile (under 600px) value; the device switcher in the panel is tied to the editor’s preview size. Defaults come first.

| Panel | Option | Values |
| --- | --- | --- |
| Layout | Layout | masonry, rows, grid, carousel, accordion (picker with diagrams) |
|  | Columns | per device: 3 · 2 · 1 |
|  | Row height | per device: 240 · 200 · 160 px – the row, tile, slide, card or strip height, depending on the layout |
|  | Row size (grid) | aspect ratio, fixed height |
|  | Aspect ratio (grid) | 1; tablet and mobile “same as larger screens” or their own |
| Items | Click behavior | link, lightbox, media file, none |
|  | Open in new tab | off (link and media file) |
|  | Hover effect | zoom, none; lift, color on hover, dim others (Pro) |
|  | Reveal on scroll | off; rise, fade; zoom, blur, tilt up, wipe, pop, flip, slide, iris (Pro). Masonry, rows, grid, carousel |
|  | Stagger (Pro) | 2.5 rem (0–6): the scroll distance between neighbours starting; 0 brings a row in together (masonry, rows, grid, with reveal on) |
| Lightbox | Style (Pro) | dark, light, frosted (blurred page behind) |
|  | Opening animation | zoom from thumbnail, fade, none |
|  | Captions, image counter | on, on |
|  | Colors (Pro) | background, text and icons, buttons |
|  | Arrow icon (Pro) | chevron, thin chevron, bold chevron, arrow, triangle (the carousel sets it under Navigation) |
|  | Download, share (Pro) | off, off: download the original upload; the system share sheet or the link to the clipboard |
|  | Camera settings (Pro) | off: camera, lens, aperture, shutter and ISO from the EXIF data, under the caption |
|  | Deep links (Pro) | off: the address names the open photo (`#gz-123`), so a link opens it; Back closes the lightbox |
| Captions | Presets (Pro) | No captions, Theme caption, Card, Gradient overlay, Hover reveal, Solid bar, Frosted glass, Clean text |
|  | Position | hidden, below, on the image, on hover |
|  | Look (Pro) | plain (gradient on images), solid, frosted glass, minimal |
|  | Alignment, size (Pro) | start, center, end; XS, S, M, L |
|  | Colors (Pro) | text, background |
| Filtering | Filter animation | move + fade, none; crossfade, slide, zoom (Pro) |
|  | Gallery ID | for filter blocks to target |
|  | Filter by (Pro) | Gallery Categories or any public taxonomy |
| Load more (Pro) | Show items | all at once; in steps, with a button; in steps, on scroll |
|  | Items per step | 12 (1–60) |
| Block supports |  | wide and full alignment, anchor, text and background color, gap (row and column), corner radius |

##### Click behavior

**Link** (the default) follows each image’s own link – nothing more. Set links per image with **Link** in the image’s block toolbar. When no image in the gallery has a link, the sidebar shows a warning, because clicks would do nothing; pick *Lightbox* or *Media file* instead if the photos should open larger.

The carousel and accordion have options of their own – arrows, autoplay, looping, centering and more. They are listed on the [Layouts](https://wispgallery.com/docs/layouts/) page.

##### Captions

Captions are hidden until you choose otherwise; their position is free, their look is a Pro setting. A caption preset sets position, look, alignment and size in one click; each preset in the picker shows a small preview of its look. Presets only set regular attributes, so every value stays editable afterwards. Captions apply to image captions and to post titles and excerpts alike. A frosted-glass caption sits inset on the photo, and its corners follow the image’s corner radius (minus the inset), so the two curves stay parallel.

##### Lightbox

Set *Click behavior* to **Lightbox** and photos open in a native `<dialog>`: it zooms out of the thumbnail, pages through the photos that are currently visible (so it respects the filter) with the arrow buttons, arrow keys or a swipe, and shows the caption and a counter. Click the photo, press `Esc` or use the close button to close it. While it is open, the page behind doesn’t scroll.

#### Gallery Filter

| Panel | Option | Values |
| --- | --- | --- |
| Presets (Pro) |  | Compact chips, Tabs, Segmented control, Soft tags, Minimal links, Theme buttons, Theme links, Pills + dropdown on mobile, Dropdown – each with a small preview in the picker |
| Styles |  | Theme button; Theme link, Soft, Pills, Segmented, Underline (Pro) |
| Appearance | Display as | buttons, dropdown on mobile, dropdown |
|  | Button size (Pro) | small, XS, M, L, theme |
|  | Dropdown label | text |
| Settings | Gallery | one gallery by ID, or all on the page |
|  | Categories | which terms to show, and in which order |
|  | Taxonomy (Pro) | Gallery Categories or any public taxonomy; must match the gallery’s |
|  | “All” button, label | on, “All” |
|  | Counts | off, on (inline); badge, superscript, number and badge colors (Pro) |
|  | Hide empty, multi-select | on, off |
|  | URL parameter (Pro) | e.g. `category` → `?category=food`, also rendered on the server |
| Block supports |  | alignment, layout (justification, orientation, wrap), gap, text, background and button colors, typography, border, shadow |

The **Theme button** style (the default) uses your theme’s button element styles; **Theme link** (Pro) uses its link color, hover and underline. Typography, border, shadow, gap and the “Button” color from the block’s own settings apply to the buttons, in the free plugin too.

#### Gallery Media Query

Put it inside a Filterable Gallery to show Media Library images by category. Settings: categories, or *Use the archive’s category* (on a Gallery Category archive: that category and its children); number of images (24); order by date, title or random, ascending or descending; image size; link to none, the file or the attachment page; captions.

#### Gallery Posts

Insert a Query Loop and choose the **Gallery Posts** variation, or add posts or pages from the empty gallery. Each post becomes an item: its Featured Image, with the Post Title – and a Post Excerpt, if you add that block to the template – as the caption. Filter posts the same way as images – by Gallery Categories, which posts and pages share with media, or by any other taxonomy such as regular post categories.

#### Wisp Gallery Pro

Pro adds editor controls to the same blocks. Everything they set is rendered by the free plugin, so galleries never change or break when a license ends; only changing those settings needs Pro again.

- **Videos:** Media Library files, YouTube (also Shorts), Vimeo, TikTok and Instagram next to photos. Add them from *Add content → Videos* or paste a link into the gallery; the provider’s thumbnail is saved as the poster. Hover plays a muted preview (*Play videos on hover*), a click plays it in the lightbox.
- **Load more:** show a large gallery in steps, with a button or on scroll.
- **Albums:** a Filterable Gallery inside a gallery becomes one cover tile, with its name and photo count, that opens its photos in the lightbox.
- **WooCommerce products:** WooCommerce’s Product Collection block inside a gallery, and a shop pattern. Product items show their price and an add to cart link in the lightbox (a Query Loop of products does that in the free plugin too).
- **Lightbox extras:** themes and colors, download and share buttons, camera settings and deep links.
- **Design controls:** caption looks and presets, filter styles and presets, button sizes, count styles and colors, carousel arrow design and arrows that follow the mouse, autoplay timing, more hover, reveal and filter effects, and filtering by any taxonomy.
- **Pattern pack:** ready-made galleries for artists, weddings, menus, property listings and more, under *Patterns → Wisp Gallery Pro*.

### Layouts

> The five Wisp Gallery layouts – masonry, justified rows, grid, carousel and accordion – how each one works, its options, and scroll reveal effects.

Source: https://wispgallery.com/docs/layouts/

One block, five layouts. Switch between them in the Layout panel at any time – the photos, captions and categories stay the same. Every layout filters, opens the lightbox and adapts to phones.

| Layout | Best for | Crops photos | JavaScript |
| --- | --- | --- | --- |
| [Masonry](#masonry) | Mixed portrait and landscape photos | No | 1 KB placement script |
| [Justified rows](#rows) | Photo-book look, even rows | No | 1 KB placement script |
| [Grid](#grid) | Products, teams, anything that should line up | Yes, to a ratio or height | None – pure CSS |
| [Carousel](#carousel) | Heroes and long series in little space | No | Navigation, loaded on demand |
| [Accordion](#accordion) | A compact strip that opens on hover or click | Closed slices only | Only for “open on click” |

Columns, row height and aspect ratio can differ per device: desktop, tablet (under 782px) and mobile (under 600px).

#### Masonry

Each photo goes into the shortest column, so the columns end at similar heights while the reading order stays left to right. A 1 KB script places the items before the first paint, so nothing jumps. Where the browser supports native masonry (`display: grid-lanes`) the script steps aside.

**Options:** columns and gap per device. Without JavaScript, masonry becomes a plain column grid.

#### Justified rows

Every row fills the full width at about the same height, and every photo keeps its proportions. Rows break where the height fits best. Built with flexbox: each item grows in proportion to its aspect ratio.

**Options:** row height per device (240 · 200 · 160 px by default), gap.

#### Grid

Cropped to one shape. Choose *aspect ratio* (1:1 by default; tablet and mobile can have their own) or *fixed height*. Pure CSS grid with `aspect-ratio` and `object-fit`.

#### Carousel

One row that scrolls sideways with scroll-snap. Swipe on phones, use the arrows or the arrow keys. Picking the carousel sets a slide height of 400 · 320 · 260 px and a 12px gap, as long as you haven’t changed those yourself.

| Option | Values |
| --- | --- |
| Arrows | on the photos (default), below the photos, none; follow the mouse (Pro: left or right third, touch swipes). Arrow keys, swiping and dragging always work. |
| Arrow design (Pro) | icon (chevron, thin chevron, bold chevron, arrow, triangle), shape (circle, rounded, square), look (frosted, solid, outline, plain), size (S, M, L) and colors. The lightbox uses the same icon. |
| Autoplay | 2 s by default; pauses on hover, on focus and off screen; off with reduced motion. Setting the time (0 = off, 0.5–10 s) needs Pro. |
| Move per click | 1 photo, a page, 2 or 3 |
| At the end | loop (endless both ways: copies of the photos fill about two screens on each side, and the row jumps back by one round while nothing on screen changes, so it never pauses at an end or shows a photo still loading; the copies are hidden from screen readers and the keyboard, and a click on one opens the real photo. If every photo fits on screen, it rewinds instead), stop, rewind to the start |
| Slides visible | 0 = the photos’ own widths, or 1–6 equal slides (at most 2 on tablets, 1 on phones) |
| Center the current slide | on by default; slides snap to the middle and the ones beside it fade back (scroll-driven, no JavaScript). Works with every end mode; “a page” then moves one slide. |
| Show scrollbar | off |

#### Accordion

Slices that open to the photo’s own width, so the open photo is never cropped. On phones the slices turn vertical. **Open a slice on:** click (default – a click on the open slice then runs the click behavior) or hover. Hover mode needs no JavaScript.

#### Reveal on scroll

Photos animate in as they scroll into view: **rise** or **fade**, and with Wisp Gallery Pro also **zoom, blur, tilt up, wipe, pop, flip, slide** (neighbours come in from alternating sides) or **iris**. The reveal is staggered: neighbouring photos arrive one after another instead of a whole row at once. It works on masonry, rows, grid and the carousel (which reveals sideways). The effect is a CSS scroll-driven animation – no JavaScript, and nothing runs with reduced motion or in browsers without scroll-driven animations; those simply show the photos.

Each photo animates over the same scroll distance from where it enters, so tall and short photos reveal alike. **Stagger** (2.5 rem by default, 0–6; changing it needs Pro) is the scroll distance between neighbours starting: larger values spread a row out more, 0 brings a row in together. It applies to masonry, rows and grid.

Turn it on under **Items → Reveal on scroll**. In the editor the reveal plays in Preview mode; autoplay and hover effects run only on the front end.

> See all five live, with their settings as switchable chips, on the [Layouts](https://wispgallery.com/layouts/) page and in the [playground](https://wispgallery.com/playground/).

Further reading: layouts in real use on the [demo pages](https://wispgallery.com/demos/) – justified rows for an [artist portfolio](https://wispgallery.com/demos/artist-portfolio/), masonry for an [illustration portfolio](https://wispgallery.com/demos/illustration-portfolio/), a carousel for a [real estate listing](https://wispgallery.com/demos/real-estate-gallery/) and the accordion for a [fashion lookbook](https://wispgallery.com/demos/fashion-lookbook/).

### Editing

> Working with Wisp Gallery in the block editor: Edit and Preview modes, filter preview, bulk categories, sorting, block transforms and command palette commands.

Source: https://wispgallery.com/docs/editing/

Galleries are edited where they live: in the block editor, with the real layout, real categories and real filters. These are the tools that make that quick.

#### Edit and Preview

The gallery’s toolbar has two modes:

- **Edit** shows any layout as a plain grid of whole photos in their order. Each photo carries a badge with its categories (“No category” in yellow), and an *Add images* tile sits at the end.
- **Preview** is the real layout, exactly as visitors see it.

Masonry, rows and grid open in Preview. Carousel and accordion open in Edit, because their real layout hides or crops photos. The mode only exists in the editor and is never saved.

#### Filter preview

Click a Gallery Filter button in the editor and the galleries in Preview mode filter right there. Use it to check the categories before you publish. Nothing is saved.

#### Categories for several images at once

1. Select one image in the gallery, then shift-click more.
2. Click the tag button in the block toolbar.
3. Tick or untick categories. A checkbox shows as mixed when only some of the selected images have that category. Create new categories right in the menu.

Changes are saved to the images in the Media Library at once, so they apply in every gallery that uses those images. A single image also has the **Gallery categories** panel in the sidebar.

#### Sort

The gallery toolbar sorts the images: newest or oldest first, title A–Z or Z–A, file name, reverse, or shuffle. Posts and media queries inside the gallery keep their places.

#### Add content

The **Add images** button and the **Add content** menu in the gallery toolbar – and the empty gallery – add **images** (several at once from the Media Library), **posts or pages** (a posts query; each item links to its page) and a **media query**.

#### Transforms

Under **block toolbar → Transform to**:

| From | To | Notes |
| --- | --- | --- |
| Core Gallery | Filterable Gallery | Images keep captions and links. Cropped galleries become a grid, uncropped ones masonry. Core’s image lightbox turns the gallery lightbox on. |
| Several selected Image blocks | Filterable Gallery |  |
| Query Loop | Filterable Gallery |  |
| Filterable Gallery | Core Gallery | When it holds only images |
| Filterable Gallery | Ungroup | Leaves the inner blocks |

#### Commands

Press `Cmd`+`K` (`Ctrl`+`K`) in the editor to open the command palette:

| Command | What it does |
| --- | --- |
| **Add filterable gallery** | Inserts a Gallery Filter and a Filterable Gallery that are already connected |
| **Edit all galleries** | Switches every gallery on the page to Edit |
| **Preview all galleries** | Switches every gallery on the page to Preview |
| **Tag selected images** | Opens the categories menu for the selected images; shown only while images in a gallery are selected |

#### Motion in the editor

Scroll reveal plays in Preview mode, so you can tune the effect and its stagger while you edit. Autoplay and hover effects are turned off in the editor, so the canvas stays calm; they run on the front end and in the post preview.

The Filterable Gallery, Gallery Filter, Gallery Media Query and Gallery Posts blocks share one icon family – tiles with a turquoise mark – so they are easy to spot in the inserter, toolbar and list view.

### Categories & Media Library

> How Gallery Categories work in Wisp Gallery: one taxonomy for images, posts and pages, plus a Media Library column, filters and bulk actions.

Source: https://wispgallery.com/docs/categories/

The filter buttons come from **Gallery Categories**: a taxonomy that images, posts and pages share. Categories live on the media item itself, so a photo tagged once is filtered correctly in every gallery it appears in.

#### The Gallery Categories taxonomy

- Taxonomy name `wisp_gallery_category`, hierarchical and public.
- Attached to media, posts and pages. Developers can add other post types with the [`wisp_gallery_taxonomy_object_types`](https://wispgallery.com/docs/developers/#hooks) filter.
- Each category has an archive at `/wisp-gallery-category/<slug>/` – in block themes it uses the [Gallery Category Archive template](https://wispgallery.com/docs/patterns/#archive-template).
- Usable in the Query Loop’s taxonomy filter, like any other taxonomy.

A gallery doesn’t have to use Gallery Categories: with Wisp Gallery Pro, under **Filtering → Filter by** it can filter by any public taxonomy, such as regular post categories or tags.

#### In the editor

- **One image:** the *Gallery categories* panel in the sidebar of an Image block inside a gallery. It saves straight to the attachment.
- **Several images:** select them and use the tag button in the block toolbar, or the *Tag selected images* command. See [Editing](https://wispgallery.com/docs/editing/#bulk-categories).
- **Posts and pages:** the Gallery Categories panel in the post sidebar.

#### In the Media Library

For tagging a whole shoot at once, the Media Library is quicker.

##### List view

- A **Gallery Categories** column. Each category links to the list filtered by it.
- A **category filter** dropdown above the list.
- Bulk actions **Add to gallery category…** and **Remove from gallery category…**, with a category picker next to them.

1. Go to **Media → Library** and switch to the list view.
2. Tick the images.
3. Choose *Add to gallery category…* in the bulk actions, pick the category and click **Apply**.

##### Grid view

A **Filter by gallery category** dropdown in the toolbar, next to core’s filters.

##### Attachment details

In the attachment details of the media modal, Gallery Categories appear as a **checklist** instead of core’s text field of comma-separated slugs.

#### Tips

- Keep the category names short: they become button labels.
- Order the buttons in the filter’s settings (*Categories*), or hide empty ones.
- Use child categories for fine-grained archives; a Media Query with *Use the archive’s category* includes the children.
- Imported galleries bring their tags along as Gallery Categories – see [Switching](https://wispgallery.com/docs/switching/).
- AI agents can tag images for you through the [Abilities API](https://wispgallery.com/docs/abilities/).

Further reading: [Filterable portfolios with posts and images in one gallery](https://wispgallery.com/blog/filterable-portfolio/), a tutorial that uses Gallery Categories on posts, images and a Media Library query.

### Switching from other plugins

> Import Envira, NextGEN, Modula and FooGallery galleries into Wisp Gallery: what carries over, the WP-CLI command and the replacement shortcodes.

Source: https://wispgallery.com/docs/switching/

Wisp Gallery imports galleries from **Envira Gallery, NextGEN Gallery, Modula** and **FooGallery**. Each gallery becomes a draft page with a Filterable Gallery of core Image blocks – and the tags become filter categories. Nothing in the old plugin is changed or deleted.

#### How the import works

- The importer reads the other plugin’s data straight from the database, so the old plugin **doesn’t need to be active** – its galleries just have to still be in the database.
- Each gallery becomes a **draft page** called “<title> (imported)”: a Gallery Filter (when the images have tags) above a Filterable Gallery of core Image blocks.
- Tags and filters become **Gallery Categories** on the images. Existing categories with the same name are reused.
- Your images stay where they are: Envira, Modula and FooGallery already use the Media Library. NextGEN keeps its own folders, so its files are **copied into the Media Library** once.
- Nothing is published until you publish the draft, and the old galleries are left untouched.

#### Import in the admin

1. Go to **Tools → Wisp Gallery import**. It lists the galleries found for each plugin, with their image count and, if already imported, a link to the page.
2. Galleries that haven’t been imported yet are ticked. Untick what you don’t want.
3. Click **Import**. The results link to each new draft page.
4. Open a draft, check the layout and categories, and publish it.

Running the import again skips galleries that were already imported. Tick *Re-import galleries that were already imported* to update their pages from the source instead.

#### What carries over

| From the old gallery | In Wisp Gallery |
| --- | --- |
| Images, in gallery order | Image blocks in a Filterable Gallery |
| Captions (or titles, where the caption is empty) | Image captions, as plain text |
| Alt text | Alt text |
| Per-image links and “open in new tab” | Image links; the gallery’s click behavior becomes *Link* |
| Lightbox on (and no custom links) | Click behavior *Lightbox* |
| Columns | Columns (up to 8) |
| Row height of justified galleries | Row height |
| Masonry or creative layout | Masonry |
| Justified or automatic layout | Justified rows |
| Any other layout | Grid |
| Tags and filters | Gallery Categories, plus a filter above the gallery |

##### Where the data comes from

| Plugin | Galleries | Become categories |
| --- | --- | --- |
| Envira Gallery | `envira` posts | Image tags (`envira-tag`, from Envira’s Tags addon) |
| NextGEN Gallery | NextGEN’s own gallery and picture tables | Picture tags (`ngg_tag`) |
| Modula | `modula-gallery` posts | Modula filters on each image |
| FooGallery | `foogallery` posts | FooGallery attachment tags and categories |

NextGEN galleries are imported as a grid with a lightbox; NextGEN’s alt text becomes the alt text and its description the caption.

#### WP-CLI

The same import from the command line – handy for many galleries or a staging run:

```
wp wisp-gallery import list
wp wisp-gallery import <envira|nextgen|modula|foogallery|all> [--ids=1,2] [--force] [--dry-run]
```

| Argument | What it does |
| --- | --- |
| `list` | Shows the galleries found, their image count and the imported page, if any |
| `envira`, `nextgen`, `modula`, `foogallery`, `all` | Imports from one plugin, or from all four |
| `--ids=1,2` | Only these source gallery IDs |
| `--force` | Re-imports galleries that were already imported, updating their pages |
| `--dry-run` | Shows what would be imported, without changing anything |

```
wp wisp-gallery import all --dry-run
wp wisp-gallery import envira --ids=12,15 --force
```

Imported pages remember their source in the `_wisp_gallery_imported_from` post meta, e.g. `envira:123`. That is what makes re-runs skip them.

#### Replacement shortcodes

Old posts often embed galleries with a shortcode. Turn on **Shortcodes** on the import page (it is off by default), and these render the imported gallery instead – even while the old plugin is still active:

- `[envira-gallery]`
- `[modula]`
- `[foogallery]`
- `[ngg src="galleries"]`, `[ngg_images]` and `[nggallery]`

> **Import first, then switch the shortcodes on.** With the option on, Wisp Gallery takes over these shortcodes completely: a shortcode whose gallery hasn’t been imported renders nothing. The imported gallery shows even while its page is still a draft.

NextGEN shortcodes for albums or tags (`[ngg src="albums"]`, `[ngg src="tags"]`) are not handled.

#### What is not carried over

The importer brings over the photos, their text and links, and a sensible layout. It does not try to recreate another plugin’s look. Expect to redo:

- **Sliders** – slider-type galleries arrive as a grid; switch the layout to [carousel](https://wispgallery.com/docs/layouts/#carousel) yourself.
- **NextGEN albums** (galleries of galleries).
- **Pagination** and “load more” settings.
- **Lightbox themes** and lightbox settings beyond on or off.
- **Spacing** (margins, gutters) and **hover effects**.
- **Caption formatting:** caption HTML becomes plain text.

After the import, pick the look you like with the gallery’s caption presets and the filter’s presets. When you are happy, you can deactivate the old plugin; keep the replacement shortcodes on if old posts still use them.

> Not sure yet? Run `wp wisp-gallery import all --dry-run` first: it lists what would be imported and changes nothing.

Further reading: [Switching from Envira, NextGEN, Modula or FooGallery](https://wispgallery.com/blog/switching-gallery-plugins/) on the blog, and the [feature comparison](https://wispgallery.com/#compare) with those plugins.

### Patterns & archive template

> The Wisp Gallery block patterns (portfolio, travel journal, food menu, team, carousels) and the Gallery Category Archive template for block themes.

Source: https://wispgallery.com/docs/patterns/

Seven ready-made layouts to start from, and a template that turns every Gallery Category into a gallery page of its own.

#### Patterns

Open the inserter, go to **Patterns → Galleries** and click one to insert it. Every pattern is made of regular blocks, so change anything afterwards.

| Pattern | What you get |
| --- | --- |
| **Photo portfolio** | Filter chips with counts above a masonry gallery of your photos, by Gallery Category, with a lightbox. |
| **Filterable portfolio** | A category filter above a masonry gallery of posts, with a lightbox. |
| **Travel journal** | A heading and intro, photos in justified rows, a quote and a carousel of more photos. |
| **Food menu** | Dishes as square photos with name and price below, filtered by course with theme-styled links. |
| **Team** | A portrait grid with names below, filtered by department. Link each photo to a profile page. |
| **Hero carousel** | A full-width carousel of wide photos that advances on its own. |
| **Latest posts carousel** | Recent posts as a carousel: featured image with the title below. |

The patterns that show photos use a **Gallery Media Query**, so they fill with your site’s own images right away. Afterwards, select the Media Query and choose which categories it shows.

Wisp Gallery Pro adds a pattern pack under **Patterns → Wisp Gallery Pro**: artist portfolio, wedding chapters, menu in pictures, property listing, fashion lookbook and more, plus a shop gallery when WooCommerce is active. Inserted patterns are regular blocks, so they keep working if a license ends.

#### Gallery Category archive template

Every Gallery Category has an archive at `/wisp-gallery-category/<slug>/`. In block themes, Wisp Gallery provides a **Gallery Category Archive** template for it (`wisp-gallery//taxonomy-wisp_gallery_category`):

- the category’s name as the title,
- its description,
- one masonry gallery with a lightbox holding the category’s **images** (a Media Query with *Use the archive’s category*) and its **posts** (a Query Loop that inherits the archive query).

So a category page is a gallery page with no extra work: tag photos and posts “Architecture”, and `/wisp-gallery-category/architecture/` shows them all.

##### Customize it

Edit the template in **Appearance → Editor → Templates → Gallery Category Archive**: change the layout, captions or lightbox like any gallery, or add blocks around it. If your theme ships its own `taxonomy-wisp_gallery_category` template, the theme’s template wins.

> The template needs WordPress 6.7 or newer (it uses `register_block_template`). Classic themes use their regular taxonomy archive.

### AI & Abilities API

> Four WordPress Abilities API abilities for AI agents and MCP clients: list categories, find images, tag images and create gallery pages, with REST methods.

Source: https://wispgallery.com/docs/abilities/

On WordPress 6.9 and newer, Wisp Gallery registers four **abilities** with the WordPress Abilities API. AI agents and MCP clients can use them to find untagged photos, tag them and build a gallery page – with the same permissions as a person doing it by hand.

#### The abilities

All four are in the category `wisp-gallery` and shown in REST.

| Ability | Input | Permission |
| --- | --- | --- |
| `wisp-gallery/list-categories` 
read-only | – | `upload_files` |
| `wisp-gallery/find-images` 
read-only | `category`, `search`, `untagged`, `limit` (up to 500, default 50) | `upload_files` |
| `wisp-gallery/tag-images` | `attachment_ids`, `categories` (names or slugs, created if missing), `mode`: add, remove or replace | `upload_files`, `assign_terms`, and `edit_post` for each image |
| `wisp-gallery/create-gallery` | `title`, `attachment_ids` or `category`, `layout`, `filter`, `columns`, `status`: draft or publish | `edit_pages` (and `publish_pages` to publish) |

##### list-categories

Lists the Gallery Categories with their ID, name, slug and number of items.

##### find-images

Finds Media Library images by Gallery Category (name or slug), by search text (title, caption and description), or only those without any category (`untagged: true`).

##### tag-images

Adds, removes or replaces Gallery Categories on images. `add` (the default) keeps existing categories, `remove` takes the given ones away, `replace` sets exactly these. Missing categories are created in add and replace mode, if the user may create categories. The whole call is refused if the user can’t edit one of the images; IDs that aren’t images are skipped and reported.

##### create-gallery

Creates a page with a gallery of the given images – or of every image in a category – optionally with filter buttons above it. `layout` is one of the five layouts (masonry by default), `columns` 1–8, and the page is a **draft** unless `status` is `publish`. It returns the page ID, edit link and view link.

#### Over REST

Abilities are listed at `/wp-json/wp-abilities/v1/abilities` and run at `/wp-json/wp-abilities/v1/abilities/<name>/run`. Core picks the HTTP method from each ability’s annotations:

| Ability | Annotations | Method |
| --- | --- | --- |
| `list-categories`, `find-images` | read-only | `GET`, input as `?input[…]` query parameters |
| `tag-images` | destructive, idempotent | `DELETE`, input as query parameters |
| `create-gallery` | – | `POST`, with a JSON body `{"input": {…}}` |

For example, with an application password:

```
curl -u admin:APP_PASSWORD \
  "https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/find-images/run?input[untagged]=1&input[limit]=20"

curl -u admin:APP_PASSWORD -X POST \
  -H "Content-Type: application/json" \
  -d '{"input":{"title":"Architecture","category":"architecture","layout":"rows","filter":false}}' \
  https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/create-gallery/run
```

#### With an AI agent

Any client that speaks the Abilities API – for example through an MCP adapter for WordPress – sees the four abilities with their descriptions and input schemas. A typical request:

1. “Find my untagged photos” → `find-images` with `untagged`.
2. The agent looks at the images and proposes categories → `tag-images`.
3. “Make a portfolio page of the architecture photos with filter buttons” → `create-gallery`, as a draft for you to review.

> The abilities check the same capabilities as the admin screens. An agent working as an Author can tag the Author’s own uploads with existing categories, but can’t create categories or pages. Nothing is published unless the agent asks for it and the user may publish pages.

On WordPress versions before 6.9 the abilities are simply not registered; everything else works as usual.

Further reading: [Letting AI agents build galleries](https://wispgallery.com/blog/ai-abilities/) on the blog, with example calls.

### Developers

> Developer notes for Wisp Gallery: WP-CLI commands, the Playground blueprint, filters and hooks, stylesheet and script handles, and how it stays fast.

Source: https://wispgallery.com/docs/developers/

Wisp Gallery is built on core blocks, the Interactivity API and plain CSS. This page covers the command line, the Playground blueprint, the hooks, and what loads when.

#### WP-CLI

```
wp plugin install wisp-gallery.zip --activate
wp wisp-gallery import list
wp wisp-gallery import <envira|nextgen|modula|foogallery|all> [--ids=1,2] [--force] [--dry-run]
```

The import command is described on [Switching from other plugins](https://wispgallery.com/docs/switching/#cli). Gallery Categories are a regular taxonomy, so core’s term commands work too:

```
wp term create wisp_gallery_category Architecture
wp post term add 123 wisp_gallery_category architecture
```

#### Live preview blueprint

The plugin ships a [WordPress Playground](https://wordpress.org/playground/) blueprint, `assets/blueprints/blueprint.json`, for the “Live Preview” button on wordpress.org. It installs the plugin, imports 12 Pexels photos in 4 categories and opens a demo page with a filter, a masonry gallery and a carousel.

It is generated from `bin/playground-demo.php` in the plugin repository:

```
npm run blueprint                                        # regenerate assets/blueprints/blueprint.json
node bin/blueprint.mjs --local <zip url> <out.json>       # variant that installs a local zip
npx @wp-playground/cli server --blueprint=<out.json>      # try it locally
```

wordpress.org reads the blueprint from the SVN `assets/blueprints/` folder, not from the plugin zip.

#### Filters

| Filter | Default | What it changes |
| --- | --- | --- |
| `wisp_gallery_taxonomy_object_types` | `['attachment', 'post', 'page']` | The post types Gallery Categories are attached to |
| `wisp_gallery_eager_items` | the desktop column count | How many items of the page’s first gallery load eagerly (the first row) |
| `wisp_gallery_image_size_width` | `480` | Width of the extra image size between WordPress’ 300px and 768px sizes |

```
// Gallery Categories for a "project" post type too.
add_filter( 'wisp_gallery_taxonomy_object_types', function ( $types ) {
	$types[] = 'project';
	return $types;
} );

// Load the first two rows eagerly.
add_filter( 'wisp_gallery_eager_items', function ( $count, $settings ) {
	return $count * 2;
}, 10, 2 );
```

#### Assets and handles

Only what a page uses loads (sizes gzipped):

| Part | Size | Loads |
| --- | --- | --- |
| `view.js` – filter, gallery core | 3.1 KB | on pages with a gallery or filter |
| `layout.js` – masonry and rows placement | 1.8 KB | when such a gallery is on the page; never for grid, carousel or accordion (pure CSS) |
| `layouts.js` – carousel navigation and loop, click accordion | 3.5 KB | when a carousel or accordion is on the page |
| `lightbox.js` | 2.9 KB | when the browser is idle, only on pages with a lightbox gallery |
| Masonry pre-paint script | 1.0 KB | inline, once, only with masonry or rows |
| Base CSS | 2.3 KB | where a gallery renders |
| Carousel, accordion, arrows, reveal and lightbox CSS | 0.5–1.1 KB each | only where a gallery uses it |

Stylesheets are enqueued from the blocks’ render callbacks: into the `<head>` on block themes, as a `<link>` right before the block on classic themes. A theme that switches layouts or options on the fly can enqueue the optional parts itself:

```
wp_enqueue_style( 'wisp-gallery-carousel' );
// Also: wisp-gallery-accordion, wisp-gallery-nav, wisp-gallery-reveal, wisp-gallery-lightbox
```

#### Performance

- **Above the fold:** the first desktop row of the page’s first gallery loads eagerly, its first image with `fetchpriority="high"`. Everything else is `loading="lazy"` with `sizes="auto"`.
- **Right-sized files:** every image gets a `sizes` value from the gallery’s columns, alignment and the theme’s content and wide size, plus the extra 480px image size.
- **No layout shift:** images carry width and height; rows and grids are pure CSS; masonry and rows are placed by a 1 KB script right after the gallery, before the first paint. CSP nonces are supported through `wp_get_inline_script_tag`.
- **Little work at runtime:** the layout engine reuses the pre-paint placement and only lays out again when something it depends on changes. Carousel autoplay pauses off screen.
- **Filtering** only animates the items that are on screen; the lightbox is created on first use.
- **No virtualization, on purpose:** a few hundred lazy items are cheap, and keeping them in the page preserves find-in-page, SEO, accessibility and correct filter counts.

Measured on a 100-photo page (logged out, fast 4G; the phone with 4× CPU slowdown):

|  | LCP | CLS | INP (filter click) | Images on first view |
| --- | --- | --- | --- | --- |
| Phone, 390px | ~260–310 ms | 0.014 | ~65 ms | 18 requests, 254 KB |
| Desktop, 1440px | ~360 ms | 0.001 | ~55 ms | 20 requests, 910 KB |

> Serving WebP or AVIF sub-sizes – with WordPress’ `image_editor_output_format` filter or the Performance Lab plugin – saves another 30–50% of image bytes.

#### How it works

- **Server rendering.** While a gallery renders, `render_block_data` and `render_block` hooks decorate each item’s opening tag with `WP_HTML_Tag_Processor`: its categories (`data-wisp-gallery-terms`), its aspect ratio for rows, lightbox data and the click behavior.
- **Front end.** One Interactivity API module, shared by gallery and filter, with no dependencies. Filtering toggles a class on items inside a View Transition, so items move to their new places; browsers without View Transitions switch instantly. The lightbox is a native `<dialog>`.
- **Layouts are CSS.** Rows are flexbox with `flex-grow` proportional to the aspect ratio; the grid is CSS grid with `aspect-ratio` and `object-fit`; masonry is CSS grid with 1px rows, and a small script puts each item in the shortest column (skipped where the browser has native masonry). Responsive values are custom properties that fall back from mobile to tablet to desktop.
- **Progressive enhancement.** Without JavaScript every item shows and links work; lightbox links point to the image file. `@starting-style`, View Transitions, `color-mix()`, `backdrop-filter` and `:has()` only add polish.

Further reading: [Custom styles for Wisp Gallery](https://wispgallery.com/blog/custom-styles/) (filter styles, caption looks, hover effects) and [why a gallery doesn’t need jQuery](https://wispgallery.com/blog/gallery-without-jquery/), with measurements from the [100-photo demo](https://wispgallery.com/100-photos/).

## Articles

### Custom styles for Wisp Gallery

> Style Wisp Gallery from your theme: the Theme button and Theme link filter styles, your own filter style with register_block_style, caption looks through CSS variables, a custom hover effect and theme.json per-block styles. Four tested examples.

Source: https://wispgallery.com/blog/custom-styles/ · Published 2026-09-23

Wisp Gallery doesn’t come with a skin of its own. The filter buttons start from your theme’s button style, captions use your theme’s fonts, and the rest is a handful of CSS classes and custom properties. That makes it easy to give a gallery your own look – in theme.json, with a block style, or with a few lines of CSS. Here are four tested examples.

Everything below goes into your theme: `theme.json` for the first example, `functions.php` for the others. If you use a theme you didn’t write, put the PHP into a child theme or a small plugin, so an update doesn’t remove it.

#### What you’re styling

Two blocks do the work. The **Gallery Filter** (`wisp-gallery/filter`) renders a row of `button.wisp-gallery-filter__button` elements – the current one has the class `is-active` and `aria-pressed="true"` – or a `select.wisp-gallery-filter__select` when it’s shown as a dropdown. The **Filterable Gallery** (`wisp-gallery/gallery`) holds a `.wisp-gallery-items` container; every photo or post inside it is a `.wisp-gallery-item`, and captions are ordinary `figcaption` elements.

The filter has six built-in styles: *Theme button* (the default), and with Wisp Gallery Pro also *Theme link*, *Soft*, *Pills*, *Segmented* and *Underline*. The styles you register yourself, as below, work in the free plugin too. Every button carries WordPress’s `wp-element-button` class, so the default style is simply your theme’s button. The other styles only change how the inactive buttons differ from the active one.

One rule to keep in mind: what you set in the block sidebar wins. Typography, border and shadow settings of the filter are written onto each button as inline styles, and the gallery writes its caption size and alignment inline too. So use CSS for what the sidebar doesn’t offer, and the sidebar for the rest.

#### 1. Theme buttons from theme.json

The quickest way to restyle every filter on a site is not to touch the filter at all. Change the theme’s button element, and the filter follows – together with every other button. To give only the filter something extra, add a block-level style for `wisp-gallery/filter`. Its buttons are button elements, so `elements.button` inside the block reaches them:

```
{
	"version": 3,
	"styles": {
		"elements": {
			"button": {
				"color": { "background": "#1f3a5f", "text": "#ffffff" },
				"border": { "radius": "6px" },
				"typography": { "fontWeight": "600" }
			},
			"link": {
				"color": { "text": "#1f3a5f" },
				":hover": { "color": { "text": "#c2410c" } }
			}
		},
		"blocks": {
			"wisp-gallery/filter": {
				"color": { "text": "#1f3a5f" },
				"elements": {
					"button": {
						"border": { "radius": "999px" },
						"typography": {
							"fontSize": "0.8125rem",
							"letterSpacing": "0.06em",
							"textTransform": "uppercase"
						}
					}
				}
			}
		}
	}
}
```

Merge these keys into the `styles` section of your existing `theme.json`. The active button now gets the navy fill of the theme button; the inactive ones are outlined in the filter’s text color, because that is what the default style does with them. The block-level rule makes this filter’s buttons pill-shaped, small and uppercase without changing the site’s other buttons.

![A filter row with uppercase pill buttons, the active All button filled in navy, above a grid of photos](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/custom-styles-1.webp)

*The theme.json example: the theme’s button color on the active button, and the block-level radius, size and letter spacing on all of them.*

The `link` element matters for the *Theme link* style: it makes the buttons look like the theme’s links, and reads the link color, hover color and underline from the global styles. Switch the filter to Theme link, and it picks up the navy and orange above.

#### 2. A filter style of your own

When the six styles aren’t enough, register another one. A block style adds the class `is-style-{name}` to the block and appears in the Styles panel next to the built-in ones, so editors can pick it like any other:

```
add_action(
	'init',
	static function () {
		register_block_style(
			'wisp-gallery/filter',
			array(
				'name'         => 'stamp',
				'label'        => __( 'Stamp', 'my-theme' ),
				'inline_style' => '
.wp-block-wisp-gallery-filter.is-style-stamp .wisp-gallery-filter__button {
	border: 2px solid currentcolor;
	border-radius: 0;
	color: inherit;
	background: transparent;
	box-shadow: 3px 3px 0 currentcolor;
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	text-transform: uppercase;
	transition: transform 0.15s, box-shadow 0.15s, background-color 0.15s;
}
.wp-block-wisp-gallery-filter.is-style-stamp .wisp-gallery-filter__button:hover {
	transform: translate(-1px, -1px);
	box-shadow: 4px 4px 0 currentcolor;
}
.wp-block-wisp-gallery-filter.is-style-stamp .wisp-gallery-filter__button.is-active {
	color: #111;
	background: #ffd84d;
	box-shadow: none;
	transform: translate(3px, 3px);
}',
			)
		);
	}
);
```

On a block theme, WordPress prints this CSS only on pages that use the filter, like the plugin’s own styles. Two details are worth copying. First, the plugin’s rules for inactive buttons only apply to its own styles, so a new style starts from the theme button and sets everything it needs itself. Second, the active button is found by `.is-active`, which the plugin keeps in step with `aria-pressed` – style that state and keyboard and screen reader users get the same feedback as everyone else. The plugin’s focus ring (a 2px outline in the text color) stays in place, because the example doesn’t touch `outline`.

![Square monospace filter buttons with hard offset shadows; the active Food button is yellow and pressed in, above three food photos](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/custom-styles-2.webp)

*The Stamp style after a click on Food: the active button drops into its shadow.*

#### 3. A caption look through CSS variables

With Wisp Gallery Pro, captions have their own presets in the block (Card, Gradient overlay, Frosted glass and more), and a text and background color. The CSS variables below work without Pro. When you want the same look on every gallery without setting colors each time, use the custom properties the captions are built on:

| Property | What it sets |
| --- | --- |
| `--wisp-gallery-cap-color` | Caption text color |
| `--wisp-gallery-cap-bg` | Caption background (a color or a gradient) |
| `--wisp-gallery-radius` | Corner radius of each photo |
| `--wisp-gallery-radius-br`, `--wisp-gallery-radius-bl` | Bottom corners, which captions on the photo follow |

The block only writes the color variables when you pick a color in the sidebar, and the radius variables when you set a radius, so a class can provide defaults for all of them. Size and alignment are always written by the block – use its settings for those.

```
add_action(
	'init',
	static function () {
		register_block_style(
			'wisp-gallery/gallery',
			array(
				'name'         => 'label',
				'label'        => __( 'Label captions', 'my-theme' ),
				'inline_style' => '
.wp-block-wisp-gallery-gallery.is-style-label {
	--wisp-gallery-cap-bg: rgb(255 255 255 / 88%);
	--wisp-gallery-cap-color: #111;
	--wisp-gallery-radius: 14px;
	--wisp-gallery-radius-br: 14px;
	--wisp-gallery-radius-bl: 14px;
}
.wp-block-wisp-gallery-gallery.is-style-label figcaption {
	font-weight: 600;
	letter-spacing: 0.01em;
}',
			)
		);
	}
);
```

Pick the style, set the captions to *on the image* and the look to *frosted glass*. The glass caption sits inset from the photo’s edges, and its corners follow the photo’s radius minus that inset, so the rounding stays even.

![Six portraits with rounded corners, each with a light, semi-transparent caption label near the bottom](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/custom-styles-3.webp)

*Label captions: a light glass label on rounded photos, set once in the theme.*

#### 4. A hover effect of your own

The built-in hover effects are zoom (the default), and with Wisp Gallery Pro lift, color on hover and dim others. For something else, set the gallery’s **Hover effect** to *None* so the two don’t mix, and add your own. This one turns every other photo grey while one is hovered:

```
add_action(
	'init',
	static function () {
		register_block_style(
			'wisp-gallery/gallery',
			array(
				'name'         => 'spotlight',
				'label'        => __( 'Spotlight', 'my-theme' ),
				'inline_style' => '
.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-item {
	overflow: hidden;
	border-radius: var(--wisp-gallery-radius, 0);
}
.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-item img {
	transition: filter 0.4s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-items:has(.wisp-gallery-item:hover) .wisp-gallery-item:not(:hover) img {
	filter: grayscale(1) brightness(0.75);
}
.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-item:is(:hover, :focus-within) img {
	transform: scale(1.04);
}
@media (prefers-reduced-motion: reduce) {
	.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-item img {
		transition: none;
	}
	.wp-block-wisp-gallery-gallery.is-style-spotlight .wisp-gallery-item:is(:hover, :focus-within) img {
		transform: none;
	}
}',
			)
		);
	}
);
```

It’s plain CSS: no script, no layout work, nothing that runs while the mouse is elsewhere. `:has()` is supported in every current browser; where it isn’t, the grey-out simply doesn’t happen and the zoom still works. The `:focus-within` part gives keyboard users the same zoom when they tab to a photo, and the reduced-motion block keeps it still for people who asked for less movement.

![A grid of six rounded photos; the hovered beach photo is in color, the other five are grey and slightly darker](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/custom-styles-4.webp)

*Spotlight while the pointer rests on the second photo.*

#### A few more hooks

- **Filter spacing:** the gap between buttons is the `--wisp-gallery-filter-gap` property (0.5rem by default); the block’s own gap setting writes it too.
- **Counts:** the numbers next to the labels are `.wisp-gallery-filter__count`, colored by `--wisp-gallery-count-color`; the badge style also reads `--wisp-gallery-count-bg`.
- **Dropdown:** the select is `.wisp-gallery-filter__select` and inherits the filter’s font and color.
- **Hidden items:** filtered-out items get `wisp-gallery-is-hidden`. Don’t style them visible – the filter relies on it.

Test your style with a keyboard once: tab through the filter, press Enter on a button, tab into the gallery. If the focus ring and the active state are both easy to see, the style is done.

> All four examples were tested with Wisp Gallery on WordPress 7.1 and Twenty Twenty-Five; the screenshots are from that test site. More class names and filters are in the [developer notes](https://wispgallery.com/docs/developers/).

Try the built-in filter looks and caption presets first in the [playground](https://wispgallery.com/playground/): it may already have the style you want.

### Switching from Envira, NextGEN, Modula or FooGallery

> How the Wisp Gallery importer moves Envira, NextGEN, Modula and FooGallery galleries into draft pages: what carries over, what doesn’t, the replacement shortcodes, WP-CLI, and what the plugins cost.

Source: https://wispgallery.com/blog/switching-gallery-plugins/ · Published 2026-09-16

Moving a site off a gallery plugin sounds like an afternoon of re-uploading photos and retyping captions. It doesn’t have to be. Wisp Gallery reads Envira Gallery, NextGEN Gallery, Modula and FooGallery galleries straight from the database and turns each one into a draft page with a filterable gallery. Here is how that works, what comes along, what doesn’t, and what the switch saves.

#### Before you start

Make a backup, as with any change that writes to the database. Then install and activate Wisp Gallery next to your current plugin. You don’t need to deactivate the old one: the importer reads its data directly, so it works whether the old plugin is active, deactivated, or already deleted – as long as its galleries are still in the database.

The import doesn’t change or delete anything that belongs to the old plugin. It only adds draft pages, and it adds Gallery Categories to your images. If you don’t like the result, delete the drafts.

#### The import, step by step

1. Go to **Tools → Wisp Gallery import** (you need to be an administrator). The page lists every gallery it found, grouped by plugin, with its image count.
2. Galleries that haven’t been imported yet are ticked. Untick the ones you don’t need.
3. Click **Import**. For each gallery you get a line with the result and a link to the new draft page.
4. Open a draft, look at the layout and the filter, and publish it – or copy its blocks into the post where the old gallery lived.

![The Import galleries screen in the WordPress admin, listing two Envira galleries, one NextGEN, one Modula and one FooGallery gallery with image counts and checkboxes, and an Import button](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/switching-import.webp)

*Tools → Wisp Gallery import on a test site with sample galleries from all four plugins.*

Each draft is called “*title* (imported)” and holds a Gallery Filter – when the images have tags – above a Filterable Gallery. The photos inside are ordinary core Image blocks, so you can edit, reorder or replace them like any other image.

Running the import again skips galleries that are already done. The importer remembers the source of each page (`envira:123`, for example, in the page’s `_wisp_gallery_imported_from` field). If you change a gallery in the old plugin and want the change over, tick *Re-import galleries that were already imported*: the existing page is updated rather than duplicated.

#### What comes along

- **Images**, in their gallery order. Envira, Modula and FooGallery already keep them in the Media Library, so nothing is copied. NextGEN keeps its own folders, so its files are copied into the Media Library once.
- **Captions and alt text.** Captions arrive as plain text: any HTML in them is stripped.
- **Links** set on single images, including “open in a new tab”. If any image has a link, the gallery’s click behavior is set to *Link*. Otherwise it opens a lightbox when the old gallery had one. Envira links that just point to the image file count as its lightbox, not as a link.
- **Columns** (up to eight) and, for justified galleries, the **row height**.
- **The layout:** masonry and Modula’s creative layout become masonry, justified and automatic layouts become justified rows, and everything else becomes a grid. NextGEN galleries become a grid with a lightbox.
- **Tags and filters:** Envira image tags, NextGEN picture tags, Modula filters and FooGallery’s tags and categories become Gallery Categories on the images. A category that already exists with the same name is reused, so two imported galleries with a “Nature” tag end up in one category.

![An imported page titled Envira Justified (imported), with pill filter buttons All, Nature, Black and White and City above a justified row of three architecture photos](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/switching-imported.webp)

*An imported Envira gallery: its justified layout became rows, its tags became the filter.*

#### What doesn’t

Some things have no equivalent, or are better set again by hand:

- **Sliders** and slideshows. Wisp Gallery has a carousel, but the importer doesn’t guess at slider settings; switch the layout to carousel afterwards if that’s what you want.
- **NextGEN albums.** The galleries in an album are imported one by one; the album itself isn’t.
- **Pagination** and “load more” buttons. A Wisp Gallery block shows all of its items and lazy-loads the images; Wisp Gallery Pro can show them in steps, with a button or on scroll.
- **Lightbox themes, spacing and hover effects.** Wisp Gallery has its own, set in the block sidebar: a dark lightbox and zoom on hover, and with Pro light or frosted lightboxes and lift, grayscale or dim-others on hover.
- **Caption formatting**, as mentioned: captions become plain text.

#### Old shortcodes keep working

Galleries are usually embedded with a shortcode, and sometimes in dozens of posts. On the import page there is an option – off by default – that makes the old shortcodes show the imported gallery instead:

| Shortcode | Shows |
| --- | --- |
| `[envira-gallery id="…"]` or `slug="…"` | The imported Envira gallery |
| `[modula id="…"]` | The imported Modula gallery |
| `[foogallery id="…"]` | The imported FooGallery gallery |
| `[ngg src="galleries" ids="…"]`, `[ngg_images gallery_ids="…"]`, `[nggallery id="…"]` | The imported NextGEN galleries |

It replaces the old plugin’s shortcodes even while that plugin is still active, which lets you compare before you switch it off. A shortcode for a gallery that wasn’t imported shows nothing, and NextGEN shortcodes that point at albums or tags aren’t covered. In the long run it’s cleaner to replace each shortcode with the gallery blocks, but there is no rush.

#### Many galleries: WP-CLI

The same importer runs from the command line. `list` shows what it finds, and `--dry-run` shows what it would do, without writing anything:

```
$ wp wisp-gallery import all --dry-run
source      id    title               images  status        note
envira      1134  Envira Justified    4       would-import  rows, 0 columns
envira      1135  Envira Grid         3       would-import  grid, 3 columns
nextgen     1     NextGEN Travel      2       would-import  grid, 0 columns
modula      1136  Modula Creative     3       would-import  masonry, 4 columns
foogallery  1137  FooGallery Masonry  3       would-import  masonry, 4 columns
```

“0 columns” means the block’s default is used. Then import one plugin, a few galleries, or everything: `wp wisp-gallery import envira --ids=1134,1135`, `wp wisp-gallery import all`, and `--force` to update pages that were already imported.

#### What the switch saves

Filter buttons are the feature most people buy a gallery add-on for. Here is what they cost with each plugin, as listed on the vendors’ pricing pages when we checked in September 2026, next to what the plugin loads on a page with a gallery and a lightbox:

| Plugin | Filter buttons | JavaScript (gzipped) |
| --- | --- | --- |
| Envira Gallery | Plus plan: $69.50 the first year, then $139 a year (3 sites) | ~29 KB + jQuery |
| Modula | Starter plan: €34 the first year, then €68 a year | ~72 KB + jQuery |
| FooGallery | PRO Expert: $79.99 a year | ~57 KB + jQuery |
| NextGEN Gallery | Search by tag, from $99.50 | ~20 KB + jQuery |
| Wisp Gallery | Free, with counts and a dropdown (five more styles in Pro, from $29 a year) | ~8 KB, no jQuery |

Over three years that is $347.50 for Envira’s Plus plan and €170 for Modula’s Starter plan. FooGallery doesn’t state its renewal price; at the listed price, three years come to $239.97. Prices change, so check the vendor’s page before you decide – the [comparison on our home page](https://wispgallery.com/#compare) lists the sources and the date.

The script sizes are the gzipped files each free version loads for a default gallery with a lightbox. jQuery adds about 35 KB if your theme doesn’t load it anyway.

#### After the import

Publish the drafts you want, or move their blocks into the original posts. Check a few galleries on a phone. When every shortcode is either replaced or covered by the replacement option, deactivate the old plugin. Its data stays in the database until you delete the plugin, so you can re-run the import later if you need to.

> The full list of what carries over, per plugin, is in the docs: [Switching from other plugins](https://wispgallery.com/docs/switching/).

### Why a gallery doesn’t need jQuery in 2026

> A native dialog lightbox, scroll-snap carousels, CSS grid and masonry, scroll-driven reveals: how Wisp Gallery starts at 3 KB of JavaScript, and what it measures on a 100-photo page.

Source: https://wispgallery.com/blog/gallery-without-jquery/ · Published 2026-09-09

For a long time a WordPress gallery meant jQuery, a masonry library, a lightbox library and an animation library, loaded on every page just in case. In 2026 the browser does most of that work itself. This is how Wisp Gallery gets by with about 8 KB of its own JavaScript, and what that looks like measured.

#### Why galleries needed all that script

A gallery has a few hard problems: placing photos of different shapes without gaps, showing one large photo over the page, scrolling a row of photos sideways, animating photos as they appear, and filtering them without a jump. Ten years ago each of these needed JavaScript – and jQuery made that JavaScript easier to write across browsers. Plugins that started then still carry that stack, and it shows: the free versions of the popular gallery plugins load between about 20 and 75 KB of gzipped JavaScript for a default gallery with a lightbox, on top of jQuery’s roughly 35 KB.

Every one of those problems now has a native answer. Here they are, one by one.

#### The lightbox is a dialog element

HTML has a `<dialog>` element. Opened as a modal, it sits in the top layer above everything else on the page, makes the rest of the page inert, closes on Escape, and returns focus to where it was when it closes. Those are exactly the parts of a lightbox that are easy to get wrong with a `div` and a lot of script.

![A dark full-screen lightbox showing a photo of a city street with an elevated train, with a counter 5 of 100, a close button and previous and next arrows](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/no-jquery-lightbox.webp)

*The lightbox on a 100-photo test page: a native dialog with a counter, arrows and the caption.*

Wisp Gallery builds its lightbox on that element and creates it the first time someone opens a photo. Its code, 2.9 KB gzipped, is only fetched when the browser is idle, and only on pages with a lightbox gallery. On top of the dialog it adds what a photo viewer needs: arrow keys, swiping, a counter, captions, and a zoom from the thumbnail. A click on the photo or around it closes it, and the page behind doesn’t scroll while it’s open. The plain fade-in is CSS `@starting-style`, and the zoom from the thumbnail uses the browser’s Web Animations API – no animation library.

#### Layouts are CSS

Three of the five layouts need no script to lay out:

- **Grid:** CSS grid, with `aspect-ratio` and `object-fit` cropping every photo to the same shape.
- **Justified rows:** flexbox. The server writes each photo’s aspect ratio into a custom property, and each item grows in proportion to it, so every row fills the width and every photo keeps its shape.
- **Accordion:** slices that open on hover need no script either; only “open on click” loads a small script.

**Masonry** is the exception – for now. CSS grid can’t yet put each photo into the shortest column everywhere, so a script does that, on a grid with 1px rows. A 1 KB inline script places the photos right after the gallery in the HTML, before the first paint, so they don’t jump into place after the page appears; a 1.8 KB module keeps them in place when the width changes. Browsers that have native masonry – `display: grid-lanes`, in Safari 26.4 and later – get pure CSS, and the script steps aside.

All of this works without JavaScript too: every item shows, rows and grids keep their layout, masonry falls back to a plain column grid, and lightbox links point to the image file.

#### The carousel is scroll-snap

A carousel used to be a strip of absolutely positioned slides moved by a script. With `scroll-snap`, it is a row that scrolls sideways and comes to rest on a photo – with the browser’s own scrolling, momentum and touch handling. Wisp Gallery adds arrows, autoplay that pauses on hover, on focus and off screen, and an endless loop: copies of the photos fill the row on both sides, and at rest the row jumps back by one round while nothing on screen changes, so it never stops at an end. The copies are hidden from screen readers and the keyboard. That navigation code (3.5 KB) only loads on pages with a carousel or a click-to-open accordion. The option to center the current slide fades its neighbors back with a scroll-driven animation, so the fade itself needs no script.

#### Reveal on scroll without an observer

Animating photos in as they scroll into view used to need a script listening to scroll events, later an `IntersectionObserver`. CSS scroll-driven animations do it declaratively: an animation whose progress is tied to the element’s position in the viewport. Wisp Gallery uses them for its ten reveal effects – rise and fade in the free plugin; zoom, blur, tilt up, wipe, pop, flip, slide and iris in Pro – with a stagger, so neighbors arrive one after another. No JavaScript runs for it at all.

Browsers without scroll-driven animations simply show the photos, and so does every browser when the visitor has asked for reduced motion. That is the pattern throughout: the modern feature adds polish, and its absence costs nothing but the polish.

#### Filtering with View Transitions

Filtering is the part that still needs a script – something has to react to the click. Wisp Gallery’s filter and gallery core is 3.1 KB. A click toggles a class on the items that don’t match, inside a View Transition, and the browser animates each photo from its old place to its new one. Before the change it only snapshots items that are on screen, so a 100-photo gallery doesn’t make the browser capture 100 images. Browsers without View Transitions switch instantly.

The script is built on the Interactivity API that ships with WordPress, the same system core blocks like Navigation and the Image lightbox use. That runtime belongs to WordPress, not to the plugin – about 15 KB gzipped in WordPress 7.1, shared by every block on the page that uses it, and loaded as a module. If your theme already uses it for its navigation, a gallery adds nothing there.

#### The numbers

Here is what the current build loads, gzipped:

| Part | Size | Loads |
| --- | --- | --- |
| Filter and gallery core | 3.1 KB | on pages with a gallery or filter |
| Masonry and rows placement | 1.8 KB | with a masonry or rows gallery |
| Masonry pre-paint script | 1.0 KB | inline, once, with masonry or rows |
| Lightbox | 2.9 KB | when the browser is idle, with a lightbox gallery |
| Carousel and accordion navigation | 3.5 KB | with a carousel or click accordion |
| Base CSS | 2.3 KB | where a gallery renders |

A filterable masonry gallery with a lightbox therefore comes to about 8 KB of Wisp Gallery script files, plus the 1 KB inline placer. On the plugin’s 100-photo test page, `window.jQuery` is undefined: the only scripts are the three Wisp Gallery files, the Interactivity runtime and WordPress’s own navigation and emoji scripts.

The plugin’s test suite measures that page, logged out on a fast 4G connection, with the phone run at four times slower CPU:

|  | Largest Contentful Paint | Layout shift | Filter click (INP) |
| --- | --- | --- | --- |
| Phone, 390px | about 260–310 ms | 0.014 | about 65 ms |
| Desktop, 1440px | about 360 ms | 0.001 | about 55 ms |

The small layout shift on the phone comes from the theme’s web font swapping in, not from the gallery. The images on first view – 18 requests and 254 KB on the phone – matter more than the script: the first row loads eagerly, its first photo with `fetchpriority="high"`, everything else lazily, and every image has a `sizes` value that matches its column.

#### What this means for your site

Less script is not a goal of its own. It means less to download on a slow connection, less to parse on a cheap phone, and less that can break when a theme or another plugin loads a different version of the same library. It also means the gallery keeps working without JavaScript, because the layout never depended on it.

You can see it for yourself: [our 100-photo demo](https://wispgallery.com/100-photos/) is a single Wisp Gallery block with a filter and a lightbox. Open the network panel, filter, scroll, and open a photo.

> Sizes: gzipped, as the plugin’s readme lists them for the current build; WordPress’s Interactivity runtime measured in WordPress 7.1.2 (minified, gzipped). Page measurements from the plugin’s performance test, as published in its readme. The competitor figures are from our [comparison](https://wispgallery.com/#compare), checked in September 2026.

How it fits together, in more detail: [How it works](https://wispgallery.com/docs/developers/#how-it-works) and the [layouts](https://wispgallery.com/docs/layouts/) in the docs.

### Filterable portfolios with posts and images in one gallery

> A step-by-step tutorial: one Wisp Gallery block with project posts, hand-picked images and a Media Library query, one category filter for all of them, and links that open the page already filtered.

Source: https://wispgallery.com/blog/filterable-portfolio/ · Published 2026-09-02

A portfolio is rarely just photos. Some work deserves a page of its own, some is a single image, and some is simply everything you’ve tagged “street”. Wisp Gallery lets you put all three into one gallery, filter them with one row of buttons, and link to the page already filtered. This tutorial builds that, step by step.

#### What we’re building

One page with a filter and a gallery that holds:

- **Project posts** – each shown with its featured image and title, and linking to the post.
- **Hand-picked images** – photos you chose and ordered yourself.
- **A Media Library query** – every image in a category, so new uploads appear without editing the page.

The glue is one taxonomy: **Gallery Categories**. It belongs to images, posts and pages alike, so a post and a photo can both be “Street”, and one filter button finds both.

![Pill filter buttons All 6, Architecture 1, Food 3, Street 2 above a grid of six photos with captions: three project posts and three food photos](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/filterable-portfolio.webp)

*One gallery, two sources: three project posts from a Query Loop and three food photos from a Media Library query, behind one filter.*

#### 1. Set up the categories

Create the categories you want to filter by – for a photographer maybe Architecture, Street, Portraits and Food. You can do that under **Media → Gallery Categories**, or on the fly while tagging, as below. Categories can be nested, and their order in the filter is set in the filter block later, so don’t worry about it now.

Then tag your content:

- **Posts:** open a project post, and tick its categories in the *Gallery Categories* panel of the sidebar. Give it a featured image – that is what the gallery shows.
- **Images in the Media Library:** in the list view, select several images and use the bulk action *Add to gallery category…*. The attachment details show the categories as a checklist too.
- **Images in a gallery:** select one, or shift-click several, and use the tag button in the block toolbar. Each category is a checkbox, and new ones can be created right there. It saves straight to the images in the Media Library.

#### 2. Add a filter and a gallery

Open the command palette (Cmd+K or Ctrl+K) and run **Add filterable gallery**. It inserts a Gallery Filter and a Filterable Gallery that are already connected. If you add them from the inserter instead, connect them yourself: give the gallery a *Gallery ID* in its Filtering panel, and pick that gallery in the filter’s settings. A filter without a gallery filters every gallery on the page, which is handy when a page has several.

The patterns are another shortcut: *Filterable portfolio (posts)* in the inserter’s Galleries category gives you a finished posts portfolio to adapt.

#### 3. Fill the gallery

An empty gallery offers three ways to add content, and you can use all of them in the same gallery:

1. **Posts or pages.** This inserts a *Gallery Posts* block: the regular Query Loop, set up with a Post Template, a Post Featured Image and a Post Title. Every Query Loop setting works – post type, order, number of posts, and the taxonomy filter, where you can limit it to certain Gallery Categories.
2. **Add images.** Pick images from the Media Library; they become ordinary Image blocks in the order you choose.
3. **A media query.** The *Gallery Media Query* block shows Media Library images by Gallery Category: choose the categories, how many (24 by default), the order – date, title or random – the image size, what they link to, and whether they show captions.

On the page, the posts, the images and the query results don’t stay in separate boxes. The groups dissolve into one grid, so a post can sit next to a photo in the same row. The toolbar’s **Sort** menu reorders the hand-picked images – newest first, by title, by file name, shuffled – and leaves the posts and queries where they are.

In the editor, the toolbar’s **Edit / Preview** switch helps here. *Edit* shows every item as a plain tile with a badge of its categories – untagged ones say “No category” in yellow – so you can spot what the filter would miss. *Preview* shows the real layout.

#### 4. Choose the look

For a portfolio of mixed content, a **grid** keeps things tidy: set the aspect ratio to 4:3 and every tile has the same shape, whether it holds a post or a photo. Masonry works too if you’d rather keep each photo’s shape. Columns can be set per device, and the default – three on desktop, two on tablets, one on phones – is a good start.

Captions apply to both kinds of items: an image shows its caption, a post its title. Put them *below* the photos for a portfolio people read, or *on hover* for a cleaner grid. Posts link to their page; for the images, pick a click behavior – a lightbox is the usual choice.

#### 5. Set up the filter

Select the filter block. In its settings:

- **Categories:** which ones get a button, and in which order. Leave it empty to show all of them.
- **“All” button:** on by default; rename it if “All work” reads better.
- **Counts:** the number of items per category, inline, as a badge or as a superscript.
- **Hide empty:** on by default, so categories with nothing in this gallery don’t get a button.
- **Multi-select:** lets visitors combine categories.

For the look, the Presets panel (Wisp Gallery Pro) sets a complete style in one click – compact chips, tabs, a segmented control, soft tags, minimal links, and more. On phones, *Pills + dropdown on mobile* keeps a long list of categories from wrapping into several rows.

#### 6. Link to a filtered view

This is the part that makes a filterable portfolio useful beyond the page itself. It is a Wisp Gallery Pro setting; links keep working if a license ends. In the filter’s settings, set a **URL parameter** – say `work`. From then on:

- Clicking “Street” changes the address to `/portfolio/?work=street`, without reloading. The link can be copied and shared.
- Opening that link shows the page with Street already selected. The active button is **rendered on the server**, and the gallery applies the selection as soon as its script starts – no click needed.
- With multi-select on, several categories are a comma-separated list: `?work=street,food`.
- A link to a category that doesn’t exist, or isn’t in this filter, shows everything instead of nothing.

![The same filter with Street selected and two street photos showing, opened from a link ending in ?work=street](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/filterable-portfolio-street.webp)

*The page opened at `?work=street`: Street is selected as the page loads.*

Now you can link to parts of your portfolio from anywhere: a menu item “Street photography” pointing to `?work=street`, a newsletter, a post that mentions your food work. It’s one page to maintain, with as many entry points as you have categories.

#### Bonus: an archive per category

Gallery Categories are a public taxonomy, so each one also has an archive at `/wisp-gallery-category/street/`. On block themes, Wisp Gallery provides a template for it: the category’s title and description, and one masonry gallery with a lightbox that holds the category’s images and its posts. You can edit it under Appearance → Editor → Templates, and a theme’s own template for the taxonomy takes precedence. For search engines and sharing, an archive page with its own title can be the better link; for a visitor who’s already on your portfolio, the filtered link keeps them there.

#### Checklist

1. Every project post has a featured image and at least one Gallery Category.
2. The gallery shows no “No category” badges in Edit mode.
3. The filter and gallery are connected, or the filter targets the whole page.
4. With Pro: the URL parameter is set, and a link with it opens the right selection.
5. On a phone, the filter fits – or turns into a dropdown.

> Every block and option used here is described in the docs: [Blocks & options](https://wispgallery.com/docs/blocks/) and [Categories & Media Library](https://wispgallery.com/docs/categories/).

Want to see finished portfolios first? The demos include an [artist portfolio](https://wispgallery.com/demos/artist-portfolio/), an [illustration portfolio](https://wispgallery.com/demos/illustration-portfolio/) and a [tattoo portfolio](https://wispgallery.com/demos/tattoo-portfolio/), each a single Wisp Gallery block with a filter.

### Letting AI agents build galleries: the Abilities API

> Wisp Gallery registers four WordPress abilities: list categories, find images, tag images and create a gallery page. What each does, the REST routes and methods, permissions, and practical uses.

Source: https://wispgallery.com/blog/ai-abilities/ · Published 2026-08-26

WordPress 6.9 added the Abilities API: a way for plugins to describe what they can do – with a name, an input and output schema, and a permission check – so that other code, including AI agents, can find and run it. Wisp Gallery registers four abilities. Together they let an assistant sort your Media Library into categories and build gallery pages from them, within the rights of the user it acts for.

#### What an ability is

An ability is a function with a contract. It has a name like `wisp-gallery/find-images`, a description written for whoever calls it, a JSON schema for its input and its output, and a permission callback. Abilities can also be annotated: *read-only*, *destructive*, *idempotent*. WordPress lists every registered ability over REST, validates the input against the schema before running it, and checks the permission as the current user.

That contract is what makes abilities useful for AI tools. An agent – whether it talks to WordPress through REST or through an MCP adapter that exposes abilities as tools – doesn’t have to guess at endpoints or scrape the admin. It reads the list, sees what each ability expects and returns, and calls it. The plugin decides what is possible; the user’s role decides what is allowed.

#### The four Wisp Gallery abilities

They are registered in the category `wisp-gallery`, only on WordPress 6.9 and later:

| Ability | What it does | Input |
| --- | --- | --- |
| `wisp-gallery/list-categories` | Lists the Gallery Categories with their item counts. Read-only. | none |
| `wisp-gallery/find-images` | Finds Media Library images by category, by search text, or those without any category. Read-only. | `category`, `search`, `untagged`, `limit` (default 50, at most 500) |
| `wisp-gallery/tag-images` | Adds, removes or replaces Gallery Categories on images; creates missing categories by name. | `attachment_ids`, `categories`, `mode` (add, remove, replace) |
| `wisp-gallery/create-gallery` | Creates a page with a gallery of the given images, or of every image in a category, optionally with filter buttons. | `title`, `attachment_ids` or `category`, `layout`, `filter`, `columns`, `status` |

#### Over REST

All four are shown in REST. The list is at `/wp-json/wp-abilities/v1/abilities`, and each ability runs at `/wp-json/wp-abilities/v1/abilities/<name>/run`. WordPress picks the HTTP method from the annotations:

- **GET** for the read-only ones, `list-categories` and `find-images`. Input goes into the query string.
- **DELETE** for `tag-images`, because it is annotated as destructive – it can remove or overwrite categories – and idempotent: running it twice has the same effect as once. Input goes into the query string too.
- **POST** for `create-gallery`, which creates something new each time. Input goes into a JSON body under `input`.

Using another method returns a 405 error. Authentication is WordPress’s own: an application password, for example, created under Users → Profile. Here are the calls against a test site, with their real responses:

```
$ curl -u "editor:APP PASSWORD" \
  "https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/list-categories/run"
[{"id":7,"name":"Architecture","slug":"architecture","count":23},
 {"id":10,"name":"Food","slug":"food","count":22},
 {"id":8,"name":"Nature","slug":"nature","count":23}, …]

$ curl -u "editor:APP PASSWORD" -G \
  "https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/find-images/run" \
  --data-urlencode "input[untagged]=true" --data-urlencode "input[limit]=3"
[{"id":681,"title":"Bagel vendor on cobblestones","alt":"Bagel vendor on cobblestones",
  "url":"https://example.com/wp-content/uploads/2026/09/pexels-photo-18186567-edited.jpeg",
  "categories":[]}]
```

The counts include posts and pages in a category, not only images. Tagging the one untagged image, and creating a draft page from three categories:

```
$ curl -u "editor:APP PASSWORD" -X DELETE -G \
  "https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/tag-images/run" \
  --data-urlencode "input[attachment_ids][]=681" \
  --data-urlencode "input[categories][]=Street"
{"updated":1,"skipped":[],"created_categories":[],
 "categories":[{"id":11,"name":"Street","slug":"street","count":24}]}

$ curl -u "editor:APP PASSWORD" -X POST -H "Content-Type: application/json" \
  "https://example.com/wp-json/wp-abilities/v1/abilities/wisp-gallery/create-gallery/run" \
  -d '{"input":{"title":"Weekend picks","attachment_ids":[165,196,150,166,195,151,164,194],
       "layout":"grid","columns":4,"filter":true}}'
{"id":1208,"status":"draft","images":8,
 "edit_link":"https://example.com/wp-admin/post.php?post=1208&action=edit",
 "view_link":"https://example.com/?page_id=1208"}
```

![A draft page titled Weekend picks with centered pill filter buttons All, Architecture, Food and Nature above a four-column grid of eight photos](https://wispgallery.com/wp-content/themes/gallery-x-site/assets/blog/ai-abilities.webp)

*The page the create-gallery call made: a pill filter and a four-column grid, saved as a draft.*

#### The details that matter

- **Categories by name.** `tag-images` and `create-gallery` accept a category’s name or slug, so an agent can say “Street” without looking up an ID. In `add` and `replace` mode, a category that doesn’t exist yet is created.
- **Honest results.** `updated` only counts images whose categories actually changed; IDs that aren’t images, or that the user can’t edit, come back in `skipped`. New categories are listed in `created_categories`.
- **Drafts by default.** `create-gallery` saves a draft unless `status` is `publish`. A person reviews the page before anyone else sees it.
- **Real blocks.** The page holds a Filterable Gallery of core Image blocks with the lightbox on – the same markup you’d get in the editor, editable like any other page. `layout` is one of masonry (the default), rows, grid, carousel or accordion; `columns` is 1 to 8.
- **Clear errors.** An unknown category returns a 404 error that names the category; a call that ends up with no images returns a 400.

#### Permissions

Every ability checks the rights of the user it runs as – the same checks as in the admin:

| Ability | Needs |
| --- | --- |
| `list-categories`, `find-images` | `upload_files` |
| `tag-images` | `upload_files`, the right to assign Gallery Categories, and `edit_post` for each image; creating a new category also needs the right to edit categories |
| `create-gallery` | `edit_pages`, and `publish_pages` to publish right away |

In practice: an Author can find and tag their own uploads but can’t build pages; an Editor can do everything, including publishing. Give an agent an application password for a user with the role you want it to have – not an administrator’s.

#### What to use it for

- **Sorting a backlog.** Ask an assistant to go through the images without a category, look at each one, and tag it. It calls `find-images` with `untagged`, then `tag-images` in batches. You check the result in the Media Library’s category column.
- **A gallery from a brief.** “Make a page with our best food photos, in a grid, with filters.” The agent finds candidates, and `create-gallery` leaves a draft for you to adjust.
- **Housekeeping.** `list-categories` shows categories with few items; `tag-images` in `replace` mode merges them onto the right images.
- **Your own scripts.** Nothing here is AI-specific. A deploy script or another plugin can call the same endpoints – or `wp_get_ability( 'wisp-gallery/find-images' )->execute()` in PHP – with the same validation and permission checks.

The abilities don’t send anything anywhere. Nothing leaves your site unless a tool you connect calls it, and then only what the user behind that tool is allowed to see.

> Reference with every input and output field: [AI & Abilities API](https://wispgallery.com/docs/abilities/) in the docs. The calls above ran against a local test site; IDs and URLs will differ on yours.

## Plugin reference

## Wisp Gallery

Filterable gallery blocks for the WordPress block editor: masonry, justified rows and grid layouts, a separate filter block, a native lightbox, and images mixed with post or media queries.

### Blocks

| Block | What it does |
| --- | --- |
| `wisp-gallery/gallery` – Filterable Gallery | Container with five layouts: **masonry** (shortest column first, reading order kept), **rows** (justified, rows break where the height fits best), **grid** (cropped to an aspect ratio or a fixed height), **carousel** (scroll-snap row: arrows on or below the photos, step, stop/rewind/endless loop, fixed slide count, centered current slide, autoplay; Pro: arrows following the mouse, the arrows' icon, shape, look, size and colors, the autoplay time) and **accordion** (slices that open to their photo's own width on hover or click, vertical on phones; with more photos than fit at 2.5rem a slice, the strip pans slowly with the mouse, no scrollbar, and swipes on touch). **Reveal on scroll** effects (rise, fade; Pro: zoom, blur, tilt, wipe, pop, flip, slide in from alternating sides, iris; the carousel reveals sideways) (scroll-driven animation, no JS), staggered so neighbors come in one after another rather than a row at once (**Stagger** slider, Pro, 0–6: the scroll distance between neighbors; 0 brings a row in together). Items animate over the same scroll distance whatever their height, and the reveal plays in the editor's Preview mode. Columns, row height and aspect ratio per device (desktop, tablet <782px, mobile <600px) with a device switcher tied to the editor preview. Click behavior: link (default; follows each image's own link, and the sidebar warns when no image has one), lightbox, media file, none. Defaults: zoom on hover, no captions. Filter animation: move + fade or none; Pro: slide, zoom, crossfade. |
| `wisp-gallery/filter` – Gallery Filter | Buttons or a dropdown that filter one gallery (by Gallery ID) or every gallery on the page. Styles: Theme button (default, uses `wp-element-button`); Pro: Theme link (the theme's link color, hover and underline from its global styles), Soft, Pills, Segmented, Underline. Sizes XS–L or theme size (Pro), gap (default 0.5rem; 1.5em for Underline and Theme link, which have no padding), counts (inline; badge and superscript in Pro; worked out on the server when the galleries are image blocks in the same post, so counts are in the first paint and empty terms are left out instead of hidden after load), multi-select, hide empty, dropdown on mobile, URL sync (Pro; `?filter=nature`, also rendered server-side). Typography, border, shadow and the "Button" color apply to the buttons. |
| `wisp-gallery/media-query` – Gallery Media Query | Media Library images by Gallery Category, inside a gallery. |
| `core/query` variation "Gallery Posts" | Posts as gallery items via the regular Query Loop / Post Template / Featured Image blocks. |

**Presets** (Pro). Each preset shows a small preview. The filter's Presets panel (Compact chips, Tabs, Segmented control, Soft tags, Minimal links, Theme buttons, Theme links, Pills + mobile dropdown, Dropdown) and the gallery's Captions panel (No captions, Theme caption, Card, Gradient overlay, Hover reveal, Solid bar, Frosted glass, Clean text) apply a complete look in one click. They only set regular attributes, so every value stays editable.

**Captions.** Position (hidden by default / below / on the image / on hover); Pro: look (plain or gradient / solid / frosted glass / minimal), alignment, size XS–L, text and background color. They apply to image captions and to post titles and excerpts alike.

**Videos** (Pro: adding and playing them). A video item is an Image block (its poster) with a video link, so it filters, lays out and zooms like any photo. Supported: Media Library files (and any .mp4/.webm/.mov URL), YouTube (also Shorts), Vimeo (also unlisted), TikTok and Instagram. *Add content → Videos* (or "Add videos" in an empty gallery) takes a link or Media Library videos: provider thumbnails are saved to the Media Library as posters (Instagram shares none, so its poster is picked by hand), and Media Library videos use their cover image or a frame captured in the editor. An image's **Video** sidebar panel sets or removes the link, or swaps in the video's own thumbnail. On the front end a play badge marks video items; resting the mouse on one plays a muted, looped preview over the poster (not on touch, with reduced motion or Save-Data; turn it off with **Play videos on hover**), and a click opens the player in the lightbox after the zoom lands. Players come from privacy-friendly domains where there is one (`youtube-nocookie.com`, Vimeo with `dnt=1`). The link is kept in the block comment only, so the saved markup is core's own.

Gallery items are **ordinary core blocks** (Image, Query Loop, Post Featured Image, Post Title), so their own settings keep working. Images, posts and media-query results can be mixed in one gallery: on the front end the groups dissolve (`display: contents`) into one shared grid.

**Gallery Categories** (`wisp_gallery_category`) is a public taxonomy shared by media, posts and pages (archives at `/wisp-gallery-category/<slug>/`, usable in the Query Loop's taxonomy filter). Image blocks inside a gallery get a "Gallery categories" sidebar field that saves straight to the attachment. A gallery can filter by any other taxonomy too (Pro).

### Editing

- **Edit / Preview** (gallery toolbar). *Edit* shows any layout as a plain grid of whole photos in their order, each with a badge of its categories ("No category" in yellow), and an "Add images" tile at the end. *Preview* is the real layout, and every gallery opens there. In Preview the first click on a gallery selects the gallery itself (a layer over the photos takes it); once it is selected, a click selects a photo. The mode is editor-only and never saved.
- **Filter preview:** clicking a Gallery Filter button in the editor filters the galleries in Preview mode, so the categories can be checked before publishing. Nothing is saved.
- **Categories for several images at once:** select one image, or shift-click several, and use the tag button in the block toolbar. Each category is a checkbox (mixed when only some of the images have it), and new categories can be created right there. Changes are saved to the images in the Media Library at once. A single image also has the "Gallery categories" sidebar panel.
- **Sort** (gallery toolbar): newest or oldest first, title A–Z or Z–A, file name, reverse, shuffle. Posts and media queries keep their places.
- **Add content:** "Add images" (multi-select from the Media Library), posts or pages (each item links to its page), a media query. The empty gallery offers the same.
- Scroll reveal plays in Preview mode; autoplay and hover effects don't run in the editor.
- **Transforms** (block toolbar → Transform to): core Gallery → Filterable Gallery (images keep captions and links; cropped galleries become a grid, uncropped ones masonry; core's image lightbox turns the gallery lightbox on), several selected Image blocks or a Query Loop → Filterable Gallery, Filterable Gallery → core Gallery (when it holds only images), and Ungroup.
- **Block icons:** Filterable Gallery, Gallery Filter, Gallery Media Query and the Gallery Posts variation share one icon family (tiles with a turquoise mark), so they are easy to spot in the inserter, toolbar and list view.

- **Commands** (command palette, Cmd/Ctrl+K): *Add filterable gallery* inserts a Gallery Filter and a Filterable Gallery that are already connected; *Edit all galleries* / *Preview all galleries* switch every gallery on the page; *Tag selected images* opens the categories menu for the selected images (shown only while images in a gallery are selected).

### WordPress integration

- **Importers** (Tools → Wisp Gallery import, or WP-CLI) for **Envira Gallery, NextGEN Gallery, Modula and FooGallery** (tested with Envira Gallery Lite 1.16, NextGEN 4.5, Modula 3.0 and FooGallery 3.3). Their data is read straight from the database, so the source plugin doesn't need to be active. Each gallery becomes a draft page "<title> (imported)" with a Gallery Filter (when the images have tags) and a Filterable Gallery of core Image blocks, in the source's image order (FooGallery's sort setting and NextGEN's sort order included; excluded NextGEN images are left out). Carried over: alt text and captions (Modula's classic editor and FooGallery keep them on the attachment; NextGEN's escaped HTML becomes plain text), per-image links and new tab, columns, row height, spacing, the tile ratio of cropped grids, and what a click does (lightbox, image file or nothing). When some images have their own link, the gallery uses links and the others open their image file, since a gallery has one click behavior. Layouts:
  - Envira: automatic → justified rows; columns → masonry, or a grid when cropped to the default size.
  - Modula: Masonry (`grid`), Creative, Custom grid, Polaroid → masonry; Justified (or `grid` with automatic columns) → rows; Uniform/Fit grid → grid; Slider → carousel.
  - FooGallery: Masonry → masonry; Justified → rows; Carousel and Image Viewer → carousel; others → grid.
  - NextGEN: a grid with the Basic Thumbnails columns and thumbnail ratio.

  Tags and filters (`envira-tag`, `ngg_tag`, Modula filters, FooGallery tags and categories; all but NextGEN's are Pro features) become Gallery Categories on the images; existing ones are reused by name. NextGEN files are copied into the Media Library once (also those NextGEN itself imported from the Media Library, which keeps no link to the original). Re-running skips imported galleries (`_wisp_gallery_imported_from` on the page, e.g. `envira:123`); re-import updates the page and reuses the copied files.
  ```bash
  wp wisp-gallery import list
  wp wisp-gallery import <envira|nextgen|modula|foogallery|all> [--ids=1,2] [--force] [--dry-run]
  ```
  **Replacement shortcodes** (option on the import page, off by default) keep old posts working: `[envira-gallery]`, `[modula]`, `[foogallery]`, `[ngg src="galleries"]` (or `gallery_ids`/`galleries`), `[ngg_images]` and `[nggallery]`, and the Envira, Modula and FooGallery blocks render the imported gallery, even while the old plugin is still active. What was not imported (like NextGEN albums, tag galleries and saved displays `[ngg id="…"]`) keeps the old plugin's output while it is active and renders nothing once it is deactivated. With the option off and the old plugin deactivated, its shortcodes show as text and its blocks render nothing. Not carried over: NextGEN albums and per-shortcode display settings, sliders' and lightboxes' own options, pagination, hover effects and caption visibility (captions show in the lightbox), FooGallery's Pro datasources.
- **Media Library.** List view: a Gallery Categories column (terms link to the filtered list), a category filter dropdown, and bulk actions *Add to gallery category…* / *Remove from gallery category…* with a category picker next to them. Grid view: a category filter. Attachment details (media modal): categories as a checklist instead of core's comma-separated slugs.
- **Abilities API** (WordPress 6.9+; skipped on older versions), so AI agents and MCP clients can work with galleries. Category `wisp-gallery`, all shown in REST (`/wp-abilities/v1/abilities`):

  | Ability | Input | Permission |
  | --- | --- | --- |
  | `wisp-gallery/list-categories` (read-only) | – | `upload_files` |
  | `wisp-gallery/find-images` (read-only) | `category`, `search`, `untagged`, `limit` (≤ 500, default 50) | `upload_files` |
  | `wisp-gallery/tag-images` | `attachment_ids`, `categories` (names or slugs, created if missing), `mode` add / remove / replace | `upload_files`, `assign_terms`, `edit_post` per image |
  | `wisp-gallery/create-gallery` | `title`, `attachment_ids` or `category`, `layout`, `filter`, `columns`, `status` draft / publish | `edit_pages` (+ `publish_pages`) |

  Over REST, core picks the method from the annotations: GET for the read-only ones, DELETE for `tag-images` (destructive and idempotent), POST for `create-gallery`.
- **Patterns** (inserter → Patterns → Galleries): Filterable portfolio (posts), Photo portfolio (filter chips + masonry of your photos), Travel journal (justified rows, quote, carousel), Food menu (square dishes by course with theme links), Team (portraits by department), Hero carousel (full width, autoplay), Latest posts carousel. Patterns that show photos use a Gallery Media Query, so they fill with the site's own images; set its categories afterwards.
- **Category archive template.** Block themes get a "Gallery Category Archive" template (`wisp-gallery//taxonomy-wisp_gallery_category`) for `/wisp-gallery-category/<slug>/`: the title, the term description, and one masonry gallery with a lightbox that holds the category's images (Media Query with *Use the archive's category*) and its posts (Query Loop inheriting the archive query). Edit it in Appearance → Editor → Templates; a theme's own `taxonomy-wisp_gallery_category` template wins. Needs WordPress 6.7+ (`register_block_template`).
- **Live preview.** `assets/blueprints/blueprint.json` is the WordPress Playground blueprint for the "Live Preview" button on wordpress.org: installs the plugin, imports 12 Pexels photos in 4 categories and opens a demo page with a filter, masonry and a carousel. It is generated from `bin/playground-demo.php` by `npm run blueprint`; `node bin/blueprint.mjs --local <zip url> <out.json>` writes a variant that installs a local zip, for testing with `npx @wp-playground/cli server --blueprint=<out.json>`. wordpress.org reads it from the SVN `assets/blueprints/` folder, not from the plugin zip.
- **Translations.** Text domain `wisp-gallery`, with bundled translations for German, Spanish, French, Portuguese (Brazil), Italian, Japanese and Dutch (`languages/wisp-gallery-<locale>.*`: `.po`/`.mo`/`.l10n.php` for PHP and the front end's lightbox and filter labels, JSON files for the editor scripts) and `languages/wisp-gallery.pot` as the template. Files in `wp-content/languages/plugins/` win over the bundled ones, for PHP and the editor scripts alike, so language packs from translate.wordpress.org and translations saved by Loco Translate (in its "System" location) override them. Texts typed into a block stay as typed: the filter's "All" button label (edit it right on the button) and its dropdown's screen-reader label only fall back to the translated "All" / "Filter gallery" while empty.

### All options

#### Filterable Gallery (`wisp-gallery/gallery`)

| Panel | Option | Values (default first) |
| --- | --- | --- |
| Layout | Layout | masonry, rows, grid, carousel, accordion (picker with diagrams). Picking the carousel sets slide height 400 / 320 / 260 px and gap 12px while those are untouched |
| | Columns | desktop 3, tablet 2, mobile 1 |
| | Row height | 240 / 200 / 160 px: row, tile, slide or strip height depending on the layout |
| | Row size (grid) | aspect ratio, fixed height |
| | Aspect ratio (grid) | 1; tablet/mobile "same as larger screens" or own |
| Layout (carousel) | Autoplay | 2 s; Pro: 0 = off, 0.5–10 s; pauses on hover, focus, off screen; off with reduced motion |
| | Move per click | 1 photo; page, 2, 3 |
| | At the end | loop (endless both ways: copies of the photos, about two screens each side, go before and after them, and the row jumps back by one round while nothing on screen changes, so no move meets an end or an unloaded photo; the copies are `aria-hidden`, unfocusable and a click on one opens the real photo's lightbox. If every photo fits on screen it rewinds instead); stop; rewind to the start |
| | Slides visible | 0 = photo widths, 1–6 equal slides (tablet ≤ 2, phone 1) |
| | Center the current slide | on; slides snap to the middle, the ones beside it fade back (scroll-driven, no JS). Works with every end mode; "Page" moves one slide |
| | Show scrollbar | off |
| Navigation (carousel) | Arrows | on the photos (default), below the photos, none; Pro: follow the mouse (left/right third; touch swipes). Arrow keys, swiping and dragging always work |
| | Arrow icon (Pro) | chevron, thin chevron, bold chevron, arrow, triangle; the lightbox uses the same one (picked in the Lightbox panel for other layouts) |
| | Shape, look, size (Pro) | circle / rounded / square; frosted (default on the photos), solid, outline, plain (white with a soft shadow on the photos); S / M / L |
| | Colors (Pro) | icon, background, border (override the look). Galleries saved with the old single "Arrows" style keep their look |
| Layout (accordion) | Open a slice on | click (a click on the open slice runs the click behavior), hover |
| Items | Click behavior | link, lightbox, media file, none |
| | Open in new tab | off (link and media file) |
| | Hover effect | zoom (default), none; Pro: lift, color on hover (grayscale), dim others |
| | Reveal on scroll | off; rise, fade; Pro: zoom, blur, tilt up, wipe, pop, flip, slide, iris (masonry, rows, grid, carousel). CSS scroll-driven animations, no JS |
| | Stagger (Pro) | 2.5 (0–6, step 0.5): scroll distance between neighbors in rem; 0 brings a row in together |
| Lightbox | Style (Pro) | dark, light, frosted (blurred page behind) |
| | Opening animation | zoom from thumbnail, fade, none |
| | Captions, image counter | on, on |
| | Colors (Pro) | background, text and icons, buttons (prev / next / close) |
| | Arrow icon (Pro) | as in Navigation (layouts without carousel arrows) |
| | Download, share (Pro) | off, off: the original upload; the system share sheet or the link to the clipboard |
| | Camera settings (Pro) | off: camera, lens, aperture, shutter and ISO from EXIF, under the caption |
| | Deep links (Pro) | off: `#gz-123` names the open photo, so a link opens it; Back closes the lightbox |
| Captions | Presets (Pro) | No captions, Theme caption, Card, Gradient overlay, Hover reveal, Solid bar, Frosted glass, Clean text |
| | Position | hidden (default), below, on the image, on hover |
| | Look (Pro) | plain (gradient on images), solid, frosted glass, minimal |
| | Alignment, size (Pro) | start / center / end; XS, S, M, L |
| | Colors (Pro) | text, background |
| Filtering | Filter animation | move + fade, none; Pro: crossfade, slide, zoom |
| | Gallery ID | for filter blocks to target |
| | Filter by (Pro) | Gallery Categories or any public taxonomy |
| Load more (Pro) | Show items | all at once; in steps, with a button; in steps, on scroll |
| | Items per step | 12 (1–60) |
| Album (Pro, nested gallery) | Album name | shown on the cover with the photo count; the first photo is the cover |
| Block supports | | wide / full alignment, anchor, text and background color, gap (row and column), corner radius |

#### Gallery Filter (`wisp-gallery/filter`)

| Panel | Option | Values (default first) |
| --- | --- | --- |
| Presets (Pro) | | Compact chips, Tabs, Segmented control, Soft tags, Minimal links, Theme buttons, Theme links, Pills + dropdown on mobile, Dropdown |
| Styles | | Theme button; Pro: Theme link, Soft, Pills, Segmented, Underline |
| Appearance | Display as | buttons, dropdown on mobile, dropdown |
| | Button size (Pro) | small, XS, M, L, theme |
| | Dropdown label | text |
| Settings | Gallery | one gallery by ID, or all on the page |
| | Taxonomy (Pro) | Gallery Categories or any public taxonomy; must match the gallery's |
| | Categories | which terms to show and in which order |
| | "All" button, label | on, "All" |
| | Counts | off, on (inline); Pro: badge, superscript, number and badge colors |
| | Hide empty, multi-select | on, off |
| | URL parameter (Pro) | e.g. `category` → `?category=food`, rendered server-side |
| Block supports | | alignment, layout (justification, orientation, wrap), gap, text / background / button colors, typography, border, shadow |

#### Gallery Media Query (`wisp-gallery/media-query`)

Categories, or *Use the archive's category* (on a Gallery Category archive, that category and its children), number of images (24), order by date / title / random, ascending / descending, image size, link to none / file / attachment page, captions.

### Performance

Measured by `tests/e2e/performance.spec.js` on the 100-photo sample page (logged out, fast 4G; phone with 4× CPU slowdown):

| | LCP | CLS | INP (filter click) | Images on first view |
| --- | --- | --- | --- | --- |
| Phone 390px | ~260–310 ms | 0.014 (theme font swap) | ~65 ms | 18 requests, 254 KB |
| Desktop 1440px | ~360 ms | 0.001 | ~55 ms | 20 requests, 910 KB |

**Only what a page uses loads** (sizes gzipped):

| Part | Size | Loads |
| --- | --- | --- |
| `view.js` (filter, gallery core) | 3.1 KB | on pages with a gallery or filter |
| `layout.js` (masonry and rows placement) | 1.8 KB | when such a gallery is on the page; never for grid, carousel, accordion (pure CSS) |
| `layouts.js` (carousel navigation and loop, click accordion) | 3.5 KB | when a carousel or accordion is on the page |
| `lightbox.js` | 2.9 KB | when a lightbox gallery first gets the pointer, focus or a touch (or on the first click); never during the page load |
| `video.js` (hover previews, players) | 1.5 KB | when a gallery with videos first gets the pointer, focus or a touch; no video file, provider script or iframe loads before a visitor hovers or clicks |
| masonry pre-paint script | 1.0 KB | inline, once, only with masonry or rows |
| Base CSS | 2.3 KB | where a gallery renders (in every theme, not site-wide) |
| Carousel / accordion / arrows / reveal / lightbox CSS | 0.5–1.1 KB each | only where a gallery uses it |

The stylesheets are enqueued from the blocks' render callbacks: into the `<head>` on block themes (WordPress may inline them), as a `<link>` right before the block on classic themes, so nothing renders unstyled. A theme that switches layouts or options on the fly can enqueue the other parts itself: `wisp-gallery-carousel`, `wisp-gallery-accordion`, `wisp-gallery-nav`, `wisp-gallery-reveal`, `wisp-gallery-lightbox`.

- **Above the fold:** the first desktop row of the page's first gallery loads eagerly, its first image with `fetchpriority="high"`; everything else is `loading="lazy"` with `sizes="auto"`. Tune with the `wisp_gallery_eager_items` filter.
- **Right-sized files:** every image gets a `sizes` value from the gallery's columns, alignment and the theme's content/wide size (row height × aspect ratio for rows), plus an extra 480px image size (`wisp_gallery_image_size_width`) between WordPress' 300px and 768px ones.
- **No layout shift:** images carry width/height; justified rows and grids are pure CSS; masonry and rows are placed by a 1 KB script right after the gallery, before the first paint (inline in the head on block themes, a small file before the gallery on classic themes; CSP nonces supported via `wp_get_inline_script_tag`).
- **Little work at runtime:** the layout engine reuses the pre-paint placement and lays out again only when something it depends on changed (gallery width; for masonry an item whose height no longer fits its rows). Rows ignore item resizes, and hover classes never trigger a layout. Carousel autoplay pauses off screen.
- **Interaction:** filtering only snapshots items that are on screen before or after the change; the rest just switch. The lightbox is created on first use.
- **No virtualization, on purpose:** 100 or even several hundred items are cheap DOM when offscreen images are lazy; keeping them in the page preserves find-in-page, SEO, accessibility and correct filter counts.
- Tip: serving WebP/AVIF sub-sizes (WordPress' `image_editor_output_format` filter or the Performance Lab plugin) saves another 30-50% of image bytes.

### How it works

- **Server rendering.** `includes/class-items.php` hooks `render_block_data` / `render_block` and decorates each item's opening tag while a gallery renders: `data-wisp-gallery-terms`, `--wisp-gallery-ar` for the rows layout, lightbox data, and the click behavior for image links. It uses `WP_HTML_Tag_Processor`.
- **Front end.** One Interactivity API module (`src/view/`, ~7 KB minified, no dependencies) shared by gallery and filter. Filtering toggles a class on items inside a View Transition, so items move to their new places; browsers without View Transitions switch instantly. The lightbox is a native `<dialog>` created on first use; a click on the photo or around it closes it, and the page behind doesn't scroll while it's open. Hover captions and effects show on hover and on keyboard focus (`:focus-visible`), so they don't stick after a click. The frosted-glass caption's corners follow the photo's radius minus the caption's inset.
- **Layouts are CSS.** Rows = flexbox with `flex-grow` proportional to the aspect ratio. Grid = CSS grid + `aspect-ratio`/`object-fit`. Masonry = CSS grid with 1px rows; `src/shared/layout.js` puts each item in the shortest column (skipped where the browser has native masonry: `display: grid-lanes`, Safari 26.4+, or the older `grid-template-rows: masonry`). Responsive values are custom properties that fall back mobile → tablet → desktop.
- **Progressive enhancement.** Without JS every item shows and links work (lightbox links point to the image file). `@starting-style`, View Transitions, `color-mix()`, `backdrop-filter` and `:has()` only add polish.
