Turns subfolders of images inside a page into galleries, with Admin2 upload UI and automatic image optimization. Grav plugin.
  • PHP 61.5%
  • CSS 20.9%
  • JavaScript 13.3%
  • Twig 4.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
David Hall 3252f6436d Add Licence section to README, fix placeholder homepage URL
README was missing a Licence section, unlike the rest of the family.
blueprints.yaml's homepage was still the yourname/grav-plugin-
subfolder-gallery placeholder from scaffolding; now points at the
real repo.
2026-08-17 06:25:43 +00:00
css Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
js Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
templates/subfolder-gallery Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
blueprints.yaml Add Licence section to README, fix placeholder homepage URL 2026-08-17 06:25:43 +00:00
CHANGELOG.md Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
LICENSE Add LICENSE, git-clone install instructions 2026-08-16 18:20:01 +00:00
README.md Add Licence section to README, fix placeholder homepage URL 2026-08-17 06:25:43 +00:00
subfolder-gallery.php Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
subfolder-gallery.yaml Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00

Subfolder Gallery

Automatically builds an image gallery from any subfolder of images inside a blog post's page folder. If a post has more than one such subfolder, each becomes its own gallery, positioned with a shortcode. Uploading is done entirely from Grav Admin2's own page editor - nothing to install or edit anywhere else, and the front end shows nothing but the rendered gallery, no extra buttons or links.

Built and tested against Grav 2.0.17 with Admin2 + the API plugin. No third-party libraries, Composer packages, or custom JavaScript admin components - it uses Grav core's own documented self@:<subfolder> file upload destination and the API plugin's onApiBlueprintResolved event, so uploads go through Admin2's native drag-and-drop file field. Image resizing uses PHP's built-in GD extension (standard on cPanel).

Known limitation: the upload field doesn't preview images that were already uploaded in a previous save - you'll see an empty dropzone each time you open the editor, even if the folder already has images in it. This is a limitation of how Admin2 handles self@: subfolder destinations for upload fields specifically. To work around it, each gallery slot also gets a read-only Browse field showing current contents (thumbnails included). Deleting an unwanted upload isn't possible from the editor - a text-field-based attempt at this didn't work, because Admin2 doesn't track changes typed into a dynamically-added field for saving. Use FTP/file manager to remove an unwanted image for now.

Install

git clone forgejo@code.hall.me.uk:david/subfolder-gallery.git user/plugins/subfolder-gallery

Or copy the subfolder-gallery folder into user/plugins/ manually.

  1. In Admin2: Plugins → Subfolder Gallery → enable it.
  2. While you're there, check Gallery folder names under "Gallery upload slots" - this is the list of subfolder names you'll use across your site (default: gallery, gallery-2, gallery-3). Each name becomes its own upload field in every page's editor.

Uploading images

Open any post in Admin2's page editor:

  • Page-root images (no subfolder): use the editor's normal Media panel, exactly as you would for any other page - nothing special needed, and this one previews existing uploads normally.

  • Subfolder galleries: open the Galleries tab. Each configured slot gives you two fields:

    • Gallery: <slot> - drag images here to upload into that subfolder.
    • Browse: <slot> - read-only, shows what's currently in the folder.

    To remove an unwanted upload, delete it via FTP/file manager - there's no in-editor delete yet (a text-field-based attempt didn't work reliably, see the limitation above).

There is nothing to configure per-post beyond that - which slots have images in them is exactly what determines which galleries exist on that page.

How galleries are found on the front end

A page's own folder is scanned for images:

  • Directly in the page folder (no subfolder) → the page-root gallery, referenced as [gallery:_page], or picked up first by a bare [gallery].
  • Inside a subfolder (e.g. gallery/, gallery-2/) → a gallery named after that subfolder, e.g. [gallery:gallery], [gallery:gallery-2].

Folders that contain a .md file (i.e. are actual Grav subpages) are always ignored, as are any folder names in exclude_folders (default: assets, thumbs, thumbnails).

Positioning galleries with the shortcode

In the post's Markdown content:

Some intro text about the trip.

[gallery:gallery]

More text about the hike.

[gallery:gallery-2]

A bare [gallery] shortcode consumes galleries in folder order (alphabetical) each time it's used. Any gallery folders not placed via a shortcode are automatically appended to the bottom of the page (auto_append, on by default) - so if you don't add any shortcodes at all, uploaded galleries still show up, just at the end of the post.

Layouts

Every gallery renders in one of six layouts, all with click-to-enlarge lightbox behaviour on top:

  • Grid — even, cropped thumbnails in a responsive grid (the classic look).
  • Masonry — natural aspect ratio, Pinterest-style flowing columns.
  • Justified — equal-height rows sized to each photo's natural aspect ratio (like Grid and Masonry's photography-portfolio cousin) - a mix of portrait and landscape photos wrap into rows without anything being cropped or looking oddly stretched next to the others.
  • Portrait — fixed, taller grid cells that never crop; a photo that doesn't fill the cell is letterboxed instead of having its edges cut off. Good when a gallery is mostly portrait-orientation shots.
  • Mosaic — a grid with occasional larger "feature" tiles mixed among regular ones, magazine-style. Like Grid, images are cropped to fit their cell - this one's about visual rhythm and variety, not preserving every aspect ratio.
  • Carousel — a horizontal scrolling slider with prev/next buttons.

Set a site-wide default in the plugin config (default_layout), and override it per gallery by adding the layout name as a third part of the shortcode:

[gallery:gallery]              " uses the site default layout
[gallery:gallery:masonry]      " forces masonry for this gallery
[gallery:gallery-2:carousel]
[gallery:beach:justified]
[gallery:portraits:portrait]
[gallery:trip:mosaic]
[gallery::carousel]            " next unplaced gallery, forced to carousel
[gallery:masonry]              " also works: shorthand for [gallery::masonry]
                                " when no folder is actually named "masonry"

The columns setting controls the column count for grid/masonry/portrait/ mosaic and the number of visible slides in the carousel. Justified doesn't use a fixed column count - row height adapts responsively instead (220px on desktop, narrower on smaller screens).

Automatic image resizing

After you save a page in Admin2, the plugin checks every image in that page's galleries and shrinks any that are wider or taller than the configured max_width / max_height (default 2000 × 2000px), re-saving at jpeg_quality (default 82) for JPEG/WebP or png_compression (default 6) for PNG. Images already within bounds are left untouched, so they aren't repeatedly re-compressed on every save. Requires PHP's GD extension (standard on cPanel); if it's unavailable, resizing is silently skipped.

Visitor downloads

When allow_downloads is on (default), each thumbnail shows a small download button, and the lightbox has one too. These use the HTML download attribute, so images open a "Save As" dialog rather than navigating to the raw image.

Configuration

Set in Admin2: Plugins → Subfolder Gallery, or in user/config/plugins/subfolder-gallery.yaml:

Option Default Description
enabled true Enable/disable the plugin
columns 3 Columns (grid/masonry) or visible slides (carousel)
default_layout grid grid, masonry, or carousel — overridable per shortcode
lightbox true Click-to-enlarge lightbox with prev/next/keyboard
allow_downloads true Show a download button on thumbnails and the lightbox
auto_append true Append galleries not placed via shortcode
gallery_slots gallery, gallery-2, gallery-3 Subfolder names that get an upload field in the editor
image_extensions jpg, jpeg, png, gif, webp Which files count as images
exclude_folders assets, thumbs, thumbnails Folder names to never treat as galleries
max_width 2000 Max width (px) before an image is resized
max_height 2000 Max height (px) before an image is resized
jpeg_quality 82 JPEG/WebP save quality (1-100)
png_compression 6 PNG compression level (0-9, lossless either way)

Notes / limitations

  • Gallery slot names are a small, site-wide configured list rather than freely typed per post - this is what lets uploads use Admin2's native file field with no custom code. If you need a one-off gallery name outside your configured slots, you can still create the subfolder and drop images in via FTP/file manager; the front end will pick it up as a gallery automatically (it just won't have its own upload field in the editor unless you add that name to gallery_slots).
  • The upload field not previewing already-uploaded files (see the top of this file) is the main rough edge - everything else (upload, resize, front-end display, downloads, layouts) works normally.
  • Images are served as static files directly from the page folder (the same mechanism Grav already uses for images referenced in Markdown), so no extra webserver configuration is needed on cPanel.
  • Thumbnails in the gallery grid are the same files as the full-size lightbox image, sized down with CSS (object-fit: cover) rather than a separate server-generated thumbnail - since uploads are already capped at a sane max size, this keeps things simple without a second image variant to manage.

Licence

MIT.