Type something to search...
How to Build a Portfolio Gallery in WordPress?

How to Build a Portfolio Gallery in WordPress?

A portfolio gallery is the single page most visitors judge you by, whether you're a photographer, designer, agency, or contractor showing finished work. It needs to load fast, look good on a phone, let visitors filter by category if you have more than a handful of pieces, and open each image at full size without navigating away from the page.

WordPress's default media library and gallery block cover the basics, but a dedicated gallery plugin adds filtering, lightboxes, and masonry layouts with far less manual work. A custom post type and shortcode gives you the same result with markup you fully control. This guide covers both, with a complete, working custom gallery shortcode built on WP_Query.

What a Good Portfolio Gallery Needs

  • A grid or masonry layout that adapts to different image aspect ratios without leaving awkward gaps.
  • A lightbox so clicking a thumbnail opens the full image without leaving the page.
  • Category filtering, once you have more than eight or ten pieces (web design vs. branding vs. photography, for example).
  • Fast-loading images, since a portfolio page is often the heaviest page on a site if images aren't properly sized and compressed.

Option 1: Use a Gallery Plugin

Envira Gallery

Envira Gallery is one of the most popular gallery plugins for WordPress, purpose-built for exactly this kind of visual portfolio display:

  • A drag-and-drop gallery builder with grid and masonry layout options.
  • A built-in lightbox for viewing full-size images without leaving the page.
  • Album support, for grouping multiple galleries (useful if your portfolio has distinct project categories).
  • Lazy loading and responsive image sizing, so large full-resolution images don't slow the initial page load.

Setup:

  1. Install and activate Envira Gallery (the free "Lite" version covers the core grid/lightbox functionality).
  2. Go to Envira Gallery > Add New, give the gallery a title, and upload your portfolio images.
  3. Under the gallery's Config tab, choose your columns, gutter spacing, and lightbox theme.
  4. Copy the shortcode shown at the top of the editor, typically [envira-gallery id="123"], and paste it onto your portfolio page.

The paid version adds masonry layouts, video galleries, and social sharing on the lightbox itself, but the free tier is enough for a straightforward grid portfolio.

If your portfolio also benefits from motion (a slideshow of hero shots at the top of the page, for instance), that's the same use case covered in adding a slider to your WordPress website, and pairs naturally with a gallery further down the same page. For video case studies specifically, see adding video to your WordPress website.

Option 2: Build a Custom Portfolio Gallery Yourself

If you want full control over the markup, filtering behavior, or lightbox styling, a custom post type with a category taxonomy plus a gallery shortcode is a complete, dependency-free alternative.

Step 1: Register a "Portfolio Item" Post Type with a Category Taxonomy

add_action( 'init', function () {
    register_post_type( 'tw_portfolio', [
        'label'        => 'Portfolio',
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'supports'     => [ 'title', 'editor', 'thumbnail' ],
        'menu_icon'    => 'dashicons-format-image',
        'rewrite'      => [ 'slug' => 'portfolio' ],
    ] );

    register_taxonomy( 'portfolio_category', 'tw_portfolio', [
        'label'        => 'Portfolio Categories',
        'public'       => true,
        'show_in_rest' => true,
        'hierarchical' => true,
        'rewrite'      => [ 'slug' => 'portfolio-category' ],
    ] );
} );

Each portfolio piece is its own post: post_title for the project name, the featured image for the thumbnail, post_content for a short description, and portfolio_category terms for filtering.

Step 2: Build the Gallery Shortcode with WP_Query

add_shortcode( 'portfolio_gallery', function ( $atts ) {
    $atts = shortcode_atts( [
        'category' => '',
        'count'    => -1,
    ], $atts, 'portfolio_gallery' );

    $args = [
        'post_type'      => 'tw_portfolio',
        'posts_per_page' => (int) $atts['count'],
        'orderby'        => 'date',
        'order'          => 'DESC',
    ];

    if ( ! empty( $atts['category'] ) ) {
        $args['tax_query'] = [
            [
                'taxonomy' => 'portfolio_category',
                'field'    => 'slug',
                'terms'    => sanitize_title( $atts['category'] ),
            ],
        ];
    }

    $items = new WP_Query( $args );

    if ( ! $items->have_posts() ) {
        return '<p>No portfolio items to show yet.</p>';
    }

    ob_start();
    ?>
    <div class="tw-portfolio-grid">
        <?php while ( $items->have_posts() ) : $items->the_post();
            $terms = get_the_terms( get_the_ID(), 'portfolio_category' );
            $cats  = $terms && ! is_wp_error( $terms ) ? wp_list_pluck( $terms, 'slug' ) : [];
            $full  = get_the_post_thumbnail_url( get_the_ID(), 'full' );
            ?>
            <a
                href="<?php echo esc_url( $full ); ?>"
                class="tw-portfolio-item"
                data-categories="<?php echo esc_attr( implode( ' ', $cats ) ); ?>"
                data-lightbox="portfolio"
                data-title="<?php the_title_attribute(); ?>"
            >
                <?php the_post_thumbnail( 'medium_large' ); ?>
                <span class="tw-portfolio-title"><?php the_title(); ?></span>
            </a>
        <?php endwhile; ?>
    </div>
    <?php
    wp_reset_postdata();
    return ob_get_clean();
} );

[portfolio_gallery] shows everything; [portfolio_gallery category="branding" count="12"] filters to a single category and caps the result at twelve items. The data-categories attribute on each item is what makes client-side filtering (Step 4 below) possible without a page reload.

Step 3: Style the Grid

.tw-portfolio-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}

.tw-portfolio-item {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 6px;
}

.tw-portfolio-item img {
  width: 100%;
  height: 220px;
  object-fit: cover;
  display: block;
  transition: transform 0.3s ease;
}

.tw-portfolio-item:hover img {
  transform: scale(1.05);
}

.tw-portfolio-title {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0.5rem 0.75rem;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
  color: #fff;
  font-size: 0.9rem;
}

Step 4: Add Category Filter Buttons and Lightbox Behavior

Add a filter bar above the shortcode's output and wire it up with plain JavaScript:

<div class="tw-portfolio-filters">
  <button data-filter="all" class="is-active">All</button>
  <button data-filter="branding">Branding</button>
  <button data-filter="web-design">Web Design</button>
</div>
document.querySelectorAll(".tw-portfolio-filters button").forEach((btn) => {
  btn.addEventListener("click", () => {
    const filter = btn.dataset.filter;

    document
      .querySelectorAll(".tw-portfolio-filters button")
      .forEach((b) => b.classList.remove("is-active"));
    btn.classList.add("is-active");

    document.querySelectorAll(".tw-portfolio-item").forEach((item) => {
      const cats = item.dataset.categories.split(" ");
      const show = filter === "all" || cats.includes(filter);
      item.style.display = show ? "" : "none";
    });
  });
});

For the lightbox itself, the data-lightbox="portfolio" attribute in Step 2 follows the convention used by the lightweight Lightbox2 script; enqueue it alongside your theme's assets, or swap in any lightbox library that reads the same data- attributes, to get a full-size image popup with no extra markup changes needed.

Optimizing Portfolio Images for Speed

A portfolio page is usually the most image-heavy page on a site, so a few things matter more here than almost anywhere else:

  • Serve appropriately sized images. The medium_large size used in the shortcode above avoids loading a full 4000px-wide photo just to display a 400px thumbnail; WordPress generates these sizes automatically on upload.
  • Compress before uploading, using a tool like TinyPNG, ShortPixel, or Squoosh, since WordPress's built-in resizing doesn't meaningfully compress the source file itself.
  • Enable lazy loading, which WordPress core does automatically for images below the fold as of WordPress 5.5, adding loading="lazy" to <img> tags rendered through standard template functions like the_post_thumbnail().
  • Use WebP or AVIF where possible, either through your image editor's export settings or a plugin that automatically serves next-gen formats to supporting browsers.

Choosing Between the Two Approaches

  • Use Envira Gallery if you want masonry layouts, a polished lightbox, and drag-and-drop management without touching code.
  • Build it yourself if you want full control over markup and filtering behavior, or you're already comfortable with custom post types and don't want another plugin's overhead.
  • Combine both by using a plugin for quick one-off galleries embedded in blog posts, and the custom taxonomy-driven approach for the main portfolio page where filtering and categorization matter most.

Frequently Asked Questions (FAQ) About WordPress Portfolio Galleries

The core Gallery block works fine for a simple, unfiltered set of images and includes basic lightbox-style expansion in recent WordPress versions. It lacks category filtering and masonry layouts though, so once your portfolio grows past a single project category, a dedicated plugin or the custom taxonomy approach above becomes more useful.

Serve appropriately sized (not full-resolution) thumbnails, compress images before upload, and rely on WordPress's automatic lazy loading for anything below the fold. A masonry or grid gallery with thirty full-resolution photos loading at once is the single most common cause of a slow portfolio page.

Yes. You can add a video thumbnail that opens a lightbox video player, or embed the video directly in the portfolio item's content area, using the same techniques covered in adding video to your WordPress website for either self-hosted or YouTube-hosted clips.

A grid layout keeps every thumbnail the same size and aspect ratio (cropping images with CSS's object-fit as shown above), while masonry preserves each image's natural aspect ratio and packs them into columns of varying height, similar to Pinterest. Masonry looks more dynamic but requires either a JavaScript layout library or CSS grid's newer masonry value, which isn't yet supported in every browser.

Once you're past roughly eight to ten items, or you have more than one distinct type of work (branding versus web design, for example), category filters meaningfully improve how easy the gallery is to scan. Below that, a single unfiltered grid is usually simpler and just as effective.

That would require a front-end submission form tied to a draft post status pending your review, similar in structure to a custom booking form's admin-post.php handler. Most portfolio plugins, including Envira Gallery, are built for an admin-managed gallery rather than open submissions, so this generally needs custom code either way.

Yes, both for accessibility (screen readers rely on it) and for image search visibility. Write specific alt text describing what's actually shown ("Logo redesign for Cedar & Vine Coffee Roasters") rather than a generic phrase like "portfolio image 4."

Conclusion

A portfolio gallery is worth getting right since it's often the page a prospective client or employer spends the most time on before deciding whether to reach out. Envira Gallery gets you a polished masonry layout and lightbox with almost no setup effort, which is the faster path for most photographers, designers, and agencies who'd rather not manage custom code.

The custom post type, taxonomy, and shortcode approach above is the better fit if you want filtering behavior and markup that match your site's design exactly, and you're comfortable maintaining a small amount of PHP and JavaScript in exchange for not carrying another plugin.

Whichever route you take, don't skip image optimization. A beautiful gallery that takes eight seconds to load on a phone will lose more visitors than a plainer one that loads instantly, and speed is just as much a part of how the portfolio performs as the design itself.

Tags :
Share :

Related Posts

Effortlessly Crafting Compelling WordPress Pages

Effortlessly Crafting Compelling WordPress Pages

As a website owner or content creator, having the ability to seamlessly add new pages to your WordPress site is crucial. Whether you're introducing a

Continue Reading
High Traffic Tips for WordPress Mastery 🚥

High Traffic Tips for WordPress Mastery 🚥

In our digital age, where online visibility is paramount, ensuring your WordPress site can handle surging traffic is crucial. Just like a finely-tune

Continue Reading
How Do I Change the WordPress Login URL?

How Do I Change the WordPress Login URL?

By default, every WordPress site's login page lives at the same predictable address: yoursite.com/wp-login.php (which also happens to redirect from

Continue Reading