Your own filter groups
Treatment, Room, Town, Brand out of the box; rename them, nest them, or add your own. Every group is a checkbox box on the photo, never free typing.
Tag each photo with the groups you define, drop one shortcode on a page, and visitors narrow the grid with filters that live in the address bar. Six filter-bar layouts, a lightbox, and an Appearance tab. No page builder required.
A client wanted a photo gallery filtered by the kind of work, the room, and the town, with a slice of it on every town page. The gallery plugins I tried either needed a page builder, charged for filters, or loaded a framework to hide a few pictures. The job is small: tag photos, draw a grid, hide what does not match. So I wrote the small one, and made the groups configurable so it works for any portfolio.
Treatment, Room, Town, Brand out of the box; rename them, nest them, or add your own. Every group is a checkbox box on the photo, never free typing.
Stacked groups, a scrolling row, dropdowns, a primary row with Refine, a sidebar, or a drawer. Put them all on one test page and let the client pick.
The chosen filters live in the page address, so a button on a town page can open the portfolio already narrowed to that town, and the back button works.
Colors, chip shape, thumbnail shape, and spacing on an Appearance tab, written as CSS variables so your theme is never touched.
Filterable Portfolio is free software, licensed under GPL-2.0-or-later, like WordPress itself.
Everything lives under Portfolio in the WordPress admin menu. Each Settings tab has its own Save button; saving one tab never changes another.
[tfp_portfolio] on a page. That is the whole portfolio with its filter bar.Decide your groups first
A group’s key becomes part of the database name for its terms. Labels can change at any time, but changing a key after photos are tagged disconnects them from their terms. Settle the keys before you tag a hundred photos.
Every entry is one photo with a title. On Portfolio → Add New:
Photos without a featured image never appear in the grid, so a draft entry can be finished later.
Portfolio → Settings → Filter Groups defines the groups. Rows run top to bottom in the same order as the filter bar; use the arrows to reorder.
| Column | What it does |
|---|---|
| Key | Short and lowercase: letters, digits, dashes, or underscores, up to 20. It is the shortcode attribute (town="avalon") and the address parameter (?town=avalon). Words WordPress itself reads from the address (page, name, s, order, and so on) are refused with a notice. |
| Label (singular / plural) | What visitors and editors see: the filter heading, the checkbox box title, and the admin submenu. |
| Nested | Lets terms have parents, for example Jersey Shore → Avalon. A photo tagged Avalon also matches Jersey Shore. |
| In filter bar | Untick to tag photos with a group without offering it as a filter; handy for a group you only use in shortcodes. |
| In caption | Prints the photo’s terms from this group under the thumbnail, separated by dots. |
| Bar shows | For nested groups: list only the parents in the filter bar (short and tidy) or every term. |
Terms are managed like categories, under the group’s own submenu (for example Portfolio → Towns) or by ticking new ones on a photo. The filter bar only lists terms that have at least one visible photo, so a filter never leads to an empty grid.
Removing a group hides it everywhere but leaves its terms in the database; adding a group with the same key brings them back. At least one group must remain.
Paste the shortcode wherever your editor runs shortcodes: a Shortcode block, a Divi Code module, an Elementor Shortcode widget, or a Kadence Advanced Text block.
[tfp_portfolio]
The Usage tab in Settings lists the same attributes with copy buttons and fills the examples with your own term slugs.
| Attribute | Values | Default | What it does |
|---|---|---|---|
<group key> | term slugs, comma-separated | — | Show only photos tagged with these terms, for example town="avalon,stone-harbor". A parent term includes its children. One attribute per group. |
limit | a number, or -1 | -1 (all) | How many photos to show. |
filters | yes | no | yes | Show the filter bar. |
layout | stack | row | dropdowns | primary | sidebar | drawer | all | from Settings | Which filter bar to draw. all adds a preview switcher. |
columns | 1–6 | from Settings | Desktop columns. The grid drops to 2 columns in containers under 860 px and to 1 under 520 px on its own. |
lightbox | yes | no | from Settings | Open photos in the lightbox, or show plain thumbnails. |
orderby | menu_order date | menu_order | date | title | modified | rand | menu_order date | Sort order. menu_order is the Order field on the photo. |
order | ASC | DESC | DESC | Ascending or descending. |
Several shortcodes on one page work; they share one set of filters, because the filters live in the page address.
All six layouts use the same filters; only the arrangement differs. Pick a site-wide default on the General tab and override it per shortcode with layout="…".
| Layout | Best for |
|---|---|
A — Stacked groups stack | A few groups with a handful of options each; everything visible. Collapses behind a Filter button on phones. |
B — Single row row | One scrolling line of options with arrows; compact, but shows little on a phone. |
C — Dropdowns dropdowns | A menu per group with a count badge; chosen filters appear as removable tags. The default. A group with a single term shows as one toggle chip. |
D — First group + Refine primary | The first group’s options in a row, the rest behind a Refine button. Good when one group matters most. |
E — Sidebar sidebar | Filters in a sticky left column beside the photos on wide screens; collapses on phones. |
F — Drawer drawer | One button opens a panel that slides in from the right. Keeps the page clean. |
Letting a client choose
Put [tfp_portfolio layout="all"] on a private test page. A “Filter style” switcher appears above the grid, the current choice is written to the address (?layout=drawer), and the chosen filters survive switching, so you can send a link that opens on a specific option. Set a real layout before the page goes live.
A town page, a service page, or a brand page can show just its own photos, with no filter bar:
[tfp_portfolio town="avalon" limit="6" filters="no"]
[tfp_portfolio treatment="shutters" brand="hunter-douglas" limit="9" filters="no" columns="3"]
Filters live in the page address, so any link or button can land on a pre-filtered portfolio. Commas mean “either”; separate groups mean “and”.
/portfolio/?treatment=shutters,blinds&town=south-jersey
shows photos tagged Shutters or Blinds, and tagged South Jersey (or any town inside it).
Portfolio → Settings → Appearance styles the portfolio without touching your theme. The values become CSS variables on the portfolio, and the site’s font is inherited.
.tfp-portfolio. Useful hooks: .tfp-chip, .tfp-chip[aria-pressed="true"], .tfp-dd summary, .tfp-item figcaption, .tfp-title, .tfp-tags, .tfp-lb.The General tab holds the wording: the Filter button label, the Refine button, the reset link, the “no results” message, and the word used in the live count (“6 of 22 photos shown”).
Clicking a photo opens it full size over the page with the title and caption. Arrow keys or the on-screen arrows move through the photos currently shown (filters apply), swiping works on touch screens, and Escape or the × closes it. Focus stays inside the viewer for keyboard users and returns to the photo you opened.
Turn it off site-wide on the General tab or per shortcode with lightbox="no"; thumbnails then show without a link.
With WP-CLI on the server, a spreadsheet saved as CSV can create the entries, attach the photos, and tag them in one run.
wp tfp import photos.csv --dry-run
wp tfp import photos.csv
image, title, caption, then one column per group key (treatment, room, town, brand with the defaults).image is a web address, a Media Library attachment ID, or a path inside wp-content/uploads (for example 2026/11/avalon-living-room.jpg after uploading the folder by SFTP).|. Nested terms are written Parent > Child; missing terms are created.--dry-run prints what would happen without writing anything.wp tfp export photos.csv writes every entry back out in the same format, the quickest way to see what the import expects. wp tfp settings prints the current settings.
The plugin checks my update server and shows new releases on the normal WordPress Updates screen like any other plugin. It also enrolls itself in WordPress auto-updates when activated, so fixes arrive without anyone logging in; switch that off on the Plugins screen if you prefer to update by hand. Check for updates now at the bottom of any Settings tab forces a check.
Deactivating leaves everything in place. Deleting the plugin removes its settings and caches. Your photos and terms are kept unless Delete data is ticked on the General tab before deleting, in which case every portfolio entry and every filter term is removed too. Images stay in the Media Library either way.
The grid is empty when no published entry has a photo, or when the shortcode’s group attributes match nothing. Check that the entries are published, each has a Photo, and the slugs in the shortcode match the terms (the Usage tab lists them).
The bar only lists terms with at least one visible photo, and only groups with In filter bar ticked. For nested groups set to “Parents only”, child towns are reachable through their parent.
Another plugin or the theme is probably stopping the plugin’s script. Look for JavaScript errors in the browser console, and check that the page is not served from a cache that strips scripts. Caching the page itself is fine; the filters run in the browser.
Use the Appearance tab first; most differences are a color or radius. For anything else, add a rule in Custom CSS scoped to .tfp-portfolio.
Set the key back to what it was and the tags return. Keys are the one thing to settle before tagging.
The result box reports the reason. “Could not reach the update server” usually means the host blocks outbound requests; ask your host to allow updates.webdevsandbox.com.
First release: the Portfolio post type with configurable filter groups, the [tfp_portfolio] shortcode with six filter-bar layouts and the preview switcher, filtering mirrored to the page address, the lightbox, the Appearance and Usage tabs, WP-CLI import and export, self-hosted updates, and an uninstaller that keeps your data unless you say otherwise.
Threshold Filterable Portfolio is free software, released under GPL-2.0-or-later. Sites that run it get updates through the normal WordPress Updates screen, from my own update server.
Filterable Portfolio is developed and supported by Spencer Taylor, Threshold Solutions, South Jersey, USA. For questions about the plugin, use the contact page.
WordPress 6.0 or newer and PHP 7.4 or newer. Any theme; no page builder or other plugins needed. WP-CLI only for the optional CSV import.
Have an idea for a feature you'd like to see in Filterable Portfolio? Drop me a note.
Rate limiting, CAPTCHA, two-factor authentication, one-time login links, hardening, and an audit log in one small plugin, with no outside services.
Download any installed plugin or theme as a ZIP from the Plugins list or a Tools page. Built on the fly, nothing stored, no outside services.
Ask me about the plugin, Google sign-in, or the security of your own site.