Hello Shopify Community! I am a newbie to the platform, I have been designing sites on Squarespace and this is my first go at a commerce site! I have been watching tutorials and scouring the app store to find a way to create a simple image carousel gallery of portfolio images (not products) that can be tagged/categorized and filtered for different pages. I have over 50 images that I want to be able to tag in 3 categories that will display on 4 different pages (one carousel display with filter 1, one carousel display with filter 2, one carousel display with filter 3, and one carousel display with all filters). What is my best way to go about doing this? As I stated, I am completely new at this and trying to figure it out as I go so any help/guidance is greatly appreciated! Thank you in advance!
Hello @hollykdesign
Shopify doesn’t have a built-in portfolio/gallery object, but there’s one native feature that already does tagging + filtering for you: Blog posts. Make each portfolio image a blog post (or group a few per post) and tag it with your category under Blog posts > tags. Shopify auto-generates a filtered page at /blogs/your-blog-handle/tagged/your-tag, so your 3 category pages and the “all” page are just links to the blog and its tag URLs, no app needed.
For the actual sliding carousel look (not just a list), you’ll still want a gallery app from the app store since there’s no native carousel block for non-product images.
Hope that helps
For 50+ non-product images used across four pages, I would avoid building four separate galleries. Keep one source of truth and let each page select a category.
The fastest no-code route is a portfolio/gallery app that explicitly supports image tags or categories, the same gallery embedded on multiple pages, a preselected category per page, mobile swipe, alt text and lazy loading. Before importing all 50 images, test four images: one from each category plus one that should appear in more than one category. Confirm the All page, each filtered page, mobile behavior and page speed.
If you want to avoid a monthly app, the clean custom route is one portfolio_item metaobject with fields for image, category, alt/title and display order, plus one reusable theme section with a page-level category setting. Shopify metaobjects can hold the reusable entries, but the carousel and filtering still need to come from the theme section or an app.
Which theme are you using, and can an image belong to more than one category? Those two details determine whether a no-code app or a small custom section is the safer fit.
Hello @hollykdesign
You can create a custom section using JavaScript for this. Here is an example from W3Schools W3Schools Tryit Editor
For Shopify, you can create a custom theme section and add it to the theme editor. Shopify provides a guide here: How to Create Your First Shopify Theme Section - Shopify
This approach would allow the section to be customized directly from the Shopify theme editor.
I was able to build this setup for you (without any app, in native way)
Results:
Steps
1. Create the data holder (once)
- Settings > Custom data > Metaobjects > Add definition.
- Name it Portfolio image. Add a field Image (type File, image), a field Category (Single line text, set it to a list so a photo can sit in more than one category), and optionally Caption.
2. Add your photos
- Open Portfolio image > Add entry. Upload the image, type its Category (for example Weddings), Save. Repeat for all 50.
- A photo can carry two categories, for example Portraits and Events. This one list is the single source of truth for every page.
3. Add the gallery section (once)
- Online Store > Themes > … > Edit code.
- Under Sections, Add a new section, name it portfolio-gallery, delete the sample content, paste the code below, Save.
{%- comment -%}
Filterable portfolio carousel, sourced from "Portfolio image" metaobjects.
One source of truth (Settings > Custom data > Portfolio image); each page
sets its own Category to show only that group. Leave Category blank for All.
{%- endcomment -%}
{%- assign filter_category = section.settings.category | strip -%}
{%- assign cw = section.settings.card_width -%}
{%- assign ch = section.settings.card_height -%}
<style>
#pf-{{ section.id }}{padding:{{ section.settings.padding_top }}px 0 {{ section.settings.padding_bottom }}px;}
#pf-{{ section.id }} .pf-head{max-width:var(--page-width,1200px);margin:0 auto 18px;padding:0 20px;}
#pf-{{ section.id }} .pf-head h2{margin:0;}
#pf-{{ section.id }} .pf-wrap{position:relative;max-width:var(--page-width,1200px);margin:0 auto;padding:0 20px;}
#pf-{{ section.id }} .pf-track{display:flex;gap:16px;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;padding:4px 2px 14px;scrollbar-width:thin;}
#pf-{{ section.id }} .pf-item{flex:0 0 auto;width:{{ cw }}px;max-width:78vw;scroll-snap-align:start;margin:0;}
#pf-{{ section.id }} .pf-item img{width:100%;height:{{ ch }}px;object-fit:cover;border-radius:{{ section.settings.radius }}px;display:block;background:#eee;}
#pf-{{ section.id }} .pf-cap{margin-top:8px;font-size:.9rem;opacity:.8;}
#pf-{{ section.id }} .pf-arrow{position:absolute;top:calc(({{ ch }}px / 2) - 4px);transform:translateY(-50%);z-index:2;width:44px;height:44px;border-radius:50%;border:0;cursor:pointer;background:rgba(0,0,0,.6);color:#fff;font-size:24px;line-height:1;display:flex;align-items:center;justify-content:center;}
#pf-{{ section.id }} .pf-prev{left:8px;}
#pf-{{ section.id }} .pf-next{right:8px;}
#pf-{{ section.id }} .pf-empty{padding:40px 20px;opacity:.7;}
</style>
<div id="pf-{{ section.id }}">
{%- if section.settings.heading != blank -%}
<div class="pf-head"><h2>{{ section.settings.heading }}</h2></div>
{%- endif -%}
<div class="pf-wrap">
<button class="pf-arrow pf-prev" aria-label="Previous" data-pf-prev>‹</button>
<div class="pf-track" data-pf-track>
{%- assign shown = 0 -%}
{%- for item in shop.metaobjects.portfolio_image.values -%}
{%- assign cats = item.category.value -%}
{%- if filter_category == blank or cats contains filter_category -%}
{%- assign media = item.image.value -%}
<figure class="pf-item">
{%- if media != blank -%}
<img src="{{ media | image_url: width: 900 }}" alt="{{ item.caption | escape }}" loading="lazy" width="{{ cw }}" height="{{ ch }}">
{%- endif -%}
{%- if item.caption != blank -%}<figcaption class="pf-cap">{{ item.caption }}</figcaption>{%- endif -%}
</figure>
{%- assign shown = shown | plus: 1 -%}
{%- endif -%}
{%- endfor -%}
{%- if shown == 0 -%}<p class="pf-empty">No portfolio images in this category yet. Add them in Settings > Custom data > Portfolio image.</p>{%- endif -%}
</div>
<button class="pf-arrow pf-next" aria-label="Next" data-pf-next>›</button>
</div>
</div>
<script>
(function(){
var root=document.getElementById('pf-{{ section.id }}');
if(!root)return;
var track=root.querySelector('[data-pf-track]');
function step(){var c=track.querySelector('.pf-item');return c?c.getBoundingClientRect().width+16:300;}
root.querySelector('[data-pf-next]').addEventListener('click',function(){track.scrollBy({left:step(),behavior:'smooth'});});
root.querySelector('[data-pf-prev]').addEventListener('click',function(){track.scrollBy({left:-step(),behavior:'smooth'});});
})();
</script>
{% schema %}
{
"name": "Portfolio gallery",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Portfolio" },
{ "type": "text", "id": "category", "label": "Category to show", "info": "Type one category exactly as tagged (e.g. Weddings). Leave blank to show all." },
{ "type": "range", "id": "card_width", "min": 180, "max": 480, "step": 20, "unit": "px", "label": "Card width", "default": 320 },
{ "type": "range", "id": "card_height", "min": 180, "max": 520, "step": 20, "unit": "px", "label": "Card height", "default": 380 },
{ "type": "range", "id": "radius", "min": 0, "max": 32, "step": 2, "unit": "px", "label": "Corner radius", "default": 12 },
{ "type": "range", "id": "padding_top", "min": 0, "max": 100, "step": 4, "unit": "px", "label": "Top padding", "default": 40 },
{ "type": "range", "id": "padding_bottom", "min": 0, "max": 100, "step": 4, "unit": "px", "label": "Bottom padding", "default": 40 }
],
"presets": [ { "name": "Portfolio gallery" } ]
}
{% endschema %}
4. Put it on your pages
- Create your four pages (Weddings, Portraits, Events, and an All page).
- Customize each page > Add section > Portfolio gallery > set Category to show. Leave it blank on the All page.
Each page pulls from the same photo list and shows only its category.
Extra Notes
- It is a swipeable carousel with arrows, and it swipes on mobile.
- Card size and corners are in the section settings.
- Because every page reads the same list, you never re-upload an image.
If you would rather not touch code: Foto Gallery, POWR Image Gallery, or Common Ninja.
Regards,
Ploqo
I’d probably keep this separate from your product setup since they’re portfolio images rather than things you’re selling. With 50 plus images, manually building four different galleries would get annoying really quickly.
I’d look at using Shopify metaobjects for the portfolio entries, with the image and category stored on each one. Then you can have the same set of images feed all four pages, with each page filtering by the category you want. That way you only have to manage each image once.
The carousel itself would probably need a bit of custom Liquid or a theme section, especially if you want the category filtering to happen without sending people to different pages. I’d build the basic gallery first and worry about the animation and filtering after you know the images are pulling through properly.
Since you’re coming from Squarespace, I’d also duplicate your Dawn theme before experimenting with the Liquid. Makes it much easier to mess around without worrying about breaking the live site.
And once you have it working, I’d run the store through SiteGuru as a general check too. With a portfolio setup like this, it’s easy to end up with pages or images that look fine visually but have little technical issues you wouldn’t notice just browsing the site.
Hi there @hollykdesign
To this end, I would steer clear of treating portfolio images as products. A tidier solution is to create a single metaobject type for each portfolio item and add fields for the image, title, and category tags. Then make a custom theme section that queries those entries and add a different carousel for each category. That way the portfolio content lives separate from your catalog, and it makes 50+ images much easier to manage. If you’re comfortable with Liquid, this is pretty easy with a custom section and some basic JS filtering.
With 50+ images and the same images appearing across multiple filtered views, I wouldn’t build four separate galleries manually. You’ll end up maintaining the same content in several places.
I’d treat the images as one library with categories/tags, then have each page show a filtered view of that same library.
The important thing is making sure whatever approach you choose lets you change an image or its category once and have that update everywhere it appears.
If this is going to keep growing, I’d optimise for easy management rather than whichever option is quickest to build today.





