Subscribe to Our Mailing List and Stay Up-to-Date! Subscribe

ACF Gallery Field Tutorial: Create Custom Image Galleries

ACF Gallery Fields enable multiple image uploads and management through drag-and-drop interfaces returning arrays of image data for custom gallery displays. From simple grid layouts and masonry galleries to lightbox integrations and responsive image galleries, the Gallery field eliminates plugin dependencies. This comprehensive tutorial teaches Gallery field setup, image array manipulation, responsive grid layouts, lightbox library integration, and advanced techniques creating professional custom image galleries with ACF PRO.

Add Gallery Field via ACF UI:

  1. Custom Fields → Field Groups
  2. Add Field
  3. Field Type: Gallery (ACF PRO)
  4. Configuration:
    • Field Label: Project Gallery
    • Field Name: project_gallery
    • Return Format: Array (recommended)
    • Library: All (uploaded to post or all)
    • Min/Max Images: Set limits
    • Insert: Append/Prepend

Field Configuration Options:

Return Format:
- Array: Full image data (recommended)
- URL: Image URLs only
- ID: Attachment IDs only

Library:
- all: All images in media library
- uploadedTo: Only images uploaded to this post

Min/Max:
- Min: 1 (require at least one image)
- Max: 20 (limit to 20 images)

Insert:
- append: Add new images to end
- prepend: Add new images to beginning

Simple Gallery Grid:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="image-gallery">
        <?php foreach ($images as $image) : ?>
            <div class="gallery-item">
                <img src="<?php echo esc_url($image['sizes']['medium']); ?>"
                     alt="<?php echo esc_attr($image['alt']); ?>"
                     title="<?php echo esc_attr($image['title']); ?>" />
            </div>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

CSS for Grid Layout:

.image-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 20px;
    padding: 20px 0;
}

.gallery-item {
    position: relative;
    overflow: hidden;
    border-radius: 8px;
}

.gallery-item img {
    width: 100%;
    height: 250px;
    object-fit: cover;
    transition: transform 0.3s ease;
}

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

Display Images with Captions:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="gallery-with-captions">
        <?php foreach ($images as $image) : ?>
            <figure class="gallery-item">
                <img src="<?php echo esc_url($image['sizes']['large']); ?>"
                     alt="<?php echo esc_attr($image['alt']); ?>" />

                <?php if ($image['caption']) : ?>
                    <figcaption>
                        <?php echo esc_html($image['caption']); ?>
                    </figcaption>
                <?php endif; ?>
            </figure>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

Caption Styling:

.gallery-with-captions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 30px;
}

.gallery-item figcaption {
    padding: 10px;
    background: #f8f9fa;
    font-size: 14px;
    color: #6c757d;
    text-align: center;
}

GLightbox Integration:

Enqueue GLightbox (functions.php):

function mytheme_gallery_lightbox() {
    // GLightbox CSS
    wp_enqueue_style(
        'glightbox',
        'https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css',
        array(),
        '3.2.0'
    );

    // GLightbox JS
    wp_enqueue_script(
        'glightbox',
        'https://cdn.jsdelivr.net/npm/glightbox/dist/js/glightbox.min.js',
        array(),
        '3.2.0',
        true
    );

    // Initialize lightbox
    wp_add_inline_script('glightbox', '
        document.addEventListener("DOMContentLoaded", function() {
            GLightbox({
                selector: ".glightbox"
            });
        });
    ');
}
add_action('wp_enqueue_scripts', 'mytheme_gallery_lightbox');

Gallery Template with Lightbox:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="lightbox-gallery">
        <?php foreach ($images as $image) : ?>
            <a href="<?php echo esc_url($image['url']); ?>"
               class="glightbox gallery-item"
               data-gallery="gallery1"
               data-description="<?php echo esc_attr($image['caption']); ?>">
                <img src="<?php echo esc_url($image['sizes']['medium']); ?>"
                     alt="<?php echo esc_attr($image['alt']); ?>" />
            </a>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

Masonry Grid Gallery:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="masonry-gallery">
        <?php foreach ($images as $index => $image) :
            // Vary heights for masonry effect
            $tall = ($index % 3 === 0) ? 'tall' : '';
            ?>

            <div class="gallery-item <?php echo esc_attr($tall); ?>">
                <a href="<?php echo esc_url($image['url']); ?>"
                   class="glightbox"
                   data-gallery="masonry">
                    <img src="<?php echo esc_url($image['sizes']['large']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />
                </a>
            </div>

        <?php endforeach; ?>
    </div>
<?php endif; ?>

Masonry CSS:

.masonry-gallery {
    column-count: 3;
    column-gap: 15px;
}

.gallery-item {
    break-inside: avoid;
    margin-bottom: 15px;
}

.gallery-item img {
    width: 100%;
    display: block;
    border-radius: 8px;
}

.gallery-item.tall {
    height: 400px;
}

@media (max-width: 768px) {
    .masonry-gallery {
        column-count: 2;
    }
}

@media (max-width: 480px) {
    .masonry-gallery {
        column-count: 1;
    }
}

Image Slider with Navigation:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="slider-gallery">
        <div class="slider-container">
            <?php foreach ($images as $index => $image) : ?>
                <div class="slide <?php echo ($index === 0) ? 'active' : ''; ?>"
                     data-slide="<?php echo esc_attr($index); ?>">
                    <img src="<?php echo esc_url($image['sizes']['large']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />

                    <?php if ($image['caption']) : ?>
                        <div class="slide-caption">
                            <?php echo esc_html($image['caption']); ?>
                        </div>
                    <?php endif; ?>
                </div>
            <?php endforeach; ?>
        </div>

        <button class="slider-prev" aria-label="Previous image">‹</button>
        <button class="slider-next" aria-label="Next image">›</button>

        <div class="slider-dots">
            <?php foreach ($images as $index => $image) : ?>
                <button class="dot <?php echo ($index === 0) ? 'active' : ''; ?>"
                        data-slide="<?php echo esc_attr($index); ?>"
                        aria-label="Go to slide <?php echo ($index + 1); ?>"></button>
            <?php endforeach; ?>
        </div>
    </div>
<?php endif; ?>

Slider JavaScript:

document.addEventListener("DOMContentLoaded", function () {
    const slides = document.querySelectorAll(".slide");
    const dots = document.querySelectorAll(".dot");
    const prevBtn = document.querySelector(".slider-prev");
    const nextBtn = document.querySelector(".slider-next");
    let currentSlide = 0;

    function showSlide(n) {
        slides.forEach((slide) => slide.classList.remove("active"));
        dots.forEach((dot) => dot.classList.remove("active"));

        if (n >= slides.length) currentSlide = 0;
        if (n < 0) currentSlide = slides.length - 1;

        slides[currentSlide].classList.add("active");
        dots[currentSlide].classList.add("active");
    }

    prevBtn.addEventListener("click", () => {
        currentSlide--;
        showSlide(currentSlide);
    });

    nextBtn.addEventListener("click", () => {
        currentSlide++;
        showSlide(currentSlide);
    });

    dots.forEach((dot, index) => {
        dot.addEventListener("click", () => {
            currentSlide = index;
            showSlide(currentSlide);
        });
    });
});

Responsive Image Sizes

Use Appropriate Image Sizes:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="responsive-gallery">
        <?php foreach ($images as $image) : ?>
            <div class="gallery-item">
                <picture>
                    <!-- Mobile: thumbnail -->
                    <source media="(max-width: 480px)"
                            srcset="<?php echo esc_url($image['sizes']['thumbnail']); ?>">

                    <!-- Tablet: medium -->
                    <source media="(max-width: 768px)"
                            srcset="<?php echo esc_url($image['sizes']['medium']); ?>">

                    <!-- Desktop: large -->
                    <img src="<?php echo esc_url($image['sizes']['large']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />
                </picture>
            </div>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

Main Image with Thumbnail Navigation:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="gallery-with-thumbs">
        <!-- Main Display -->
        <div class="main-image">
            <img id="main-gallery-img"
                 src="<?php echo esc_url($images[0]['sizes']['large']); ?>"
                 alt="<?php echo esc_attr($images[0]['alt']); ?>" />
        </div>

        <!-- Thumbnails -->
        <div class="thumbnail-strip">
            <?php foreach ($images as $index => $image) : ?>
                <button class="thumb <?php echo ($index === 0) ? 'active' : ''; ?>"
                        data-full="<?php echo esc_url($image['sizes']['large']); ?>"
                        data-alt="<?php echo esc_attr($image['alt']); ?>">
                    <img src="<?php echo esc_url($image['sizes']['thumbnail']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />
                </button>
            <?php endforeach; ?>
        </div>
    </div>
<?php endif; ?>

Thumbnail Navigation JavaScript:

document.querySelectorAll(".thumb").forEach(function (thumb) {
    thumb.addEventListener("click", function () {
        const fullSrc = this.getAttribute("data-full");
        const altText = this.getAttribute("data-alt");
        const mainImg = document.getElementById("main-gallery-img");

        mainImg.src = fullSrc;
        mainImg.alt = altText;

        document.querySelectorAll(".thumb").forEach((t) => t.classList.remove("active"));
        this.classList.add("active");
    });
});

Performance Optimization:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="lazy-gallery">
        <?php foreach ($images as $index => $image) :
            // Load first 3 images immediately, lazy load rest
            $loading = ($index < 3) ? 'eager' : 'lazy';
            ?>

            <div class="gallery-item">
                <img src="<?php echo esc_url($image['sizes']['medium']); ?>"
                     alt="<?php echo esc_attr($image['alt']); ?>"
                     loading="<?php echo esc_attr($loading); ?>"
                     width="<?php echo esc_attr($image['sizes']['medium-width']); ?>"
                     height="<?php echo esc_attr($image['sizes']['medium-height']); ?>" />
            </div>

        <?php endforeach; ?>
    </div>
<?php endif; ?>

Gallery with Category Filters:

<?php
$images = get_field('project_gallery');

if ($images) :
    ?>
    <div class="filtered-gallery">
        <!-- Category Filters -->
        <div class="gallery-filters">
            <button class="filter-btn active" data-filter="all">All</button>
            <button class="filter-btn" data-filter="product">Product</button>
            <button class="filter-btn" data-filter="lifestyle">Lifestyle</button>
            <button class="filter-btn" data-filter="detail">Detail</button>
        </div>

        <!-- Gallery Items -->
        <div class="gallery-grid">
            <?php foreach ($images as $image) :
                // Get category from image description
                $category = $image['description'] ?: 'all';
                ?>

                <div class="gallery-item" data-category="<?php echo esc_attr($category); ?>">
                    <img src="<?php echo esc_url($image['sizes']['medium']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />
                </div>

            <?php endforeach; ?>
        </div>
    </div>
<?php endif; ?>

Filter JavaScript:

document.querySelectorAll(".filter-btn").forEach(function (btn) {
    btn.addEventListener("click", function () {
        const filter = this.getAttribute("data-filter");

        document.querySelectorAll(".filter-btn").forEach((b) => b.classList.remove("active"));
        this.classList.add("active");

        document.querySelectorAll(".gallery-item").forEach(function (item) {
            const category = item.getAttribute("data-category");

            if (filter === "all" || category === filter) {
                item.style.display = "block";
            } else {
                item.style.display = "none";
            }
        });
    });
});

Display Image Count:

<?php
$images = get_field('project_gallery');

if ($images) :
    $image_count = count($images);
    ?>

    <div class="gallery-section">
        <div class="gallery-header">
            <h2>Project Gallery</h2>
            <p class="image-count">
                <?php echo esc_html($image_count); ?>
                <?php echo ($image_count === 1) ? 'Image' : 'Images'; ?>
            </p>
        </div>

        <div class="image-gallery">
            <?php foreach ($images as $image) : ?>
                <div class="gallery-item">
                    <img src="<?php echo esc_url($image['sizes']['medium']); ?>"
                         alt="<?php echo esc_attr($image['alt']); ?>" />
                </div>
            <?php endforeach; ?>
        </div>
    </div>

<?php endif; ?>

Accessing Image Metadata

Full Image Data Available:

<?php
$images = get_field('project_gallery');

if ($images) :
    foreach ($images as $image) :
        // Available image data:
        $id = $image['ID'];
        $title = $image['title'];
        $alt = $image['alt'];
        $caption = $image['caption'];
        $description = $image['description'];
        $url = $image['url'];
        $width = $image['width'];
        $height = $image['height'];

        // Image sizes
        $thumbnail = $image['sizes']['thumbnail'];
        $medium = $image['sizes']['medium'];
        $large = $image['sizes']['large'];
        $full = $image['sizes']['full'];

        // Size dimensions
        $thumb_width = $image['sizes']['thumbnail-width'];
        $thumb_height = $image['sizes']['thumbnail-height'];
    endforeach;
endif;
?>

Register Gallery Field via PHP:

function mytheme_register_gallery_field() {
    if (function_exists('acf_add_local_field_group')) {
        acf_add_local_field_group(array(
            'key'    => 'group_project_gallery',
            'title'  => 'Project Gallery',
            'fields' => array(
                array(
                    'key'    => 'field_project_gallery',
                    'label'  => 'Gallery Images',
                    'name'   => 'project_gallery',
                    'type'   => 'gallery',
                    'return_format' => 'array',
                    'library' => 'all',
                    'min'    => 1,
                    'max'    => 50,
                    'insert' => 'append',
                ),
            ),
            'location' => array(
                array(
                    array(
                        'param'    => 'post_type',
                        'operator' => '==',
                        'value'    => 'portfolio',
                    ),
                ),
            ),
        ));
    }
}
add_action('acf/init', 'mytheme_register_gallery_field');

Best Practices

Always Check for Images:

<?php
$images = get_field('project_gallery');

// Good - check before loop
if ($images) {
    foreach ($images as $image) {
        // Display images
    }
}

// Bad - no check, may cause errors
foreach (get_field('project_gallery') as $image) {
    // Display images
}
?>

Optimize Image Sizes:

// Use appropriate size for context
$thumbnail = $image['sizes']['thumbnail']; // 150x150
$medium = $image['sizes']['medium'];       // 300x300
$large = $image['sizes']['large'];         // 1024x1024

Escape All Output:

<?php echo esc_url($image['url']); ?>
<?php echo esc_attr($image['alt']); ?>
<?php echo esc_html($image['caption']); ?>

Conclusion

ACF Gallery Fields enable custom image galleries through drag-and-drop interfaces returning comprehensive image data arrays for flexible display options. Implement responsive grid layouts with CSS Grid, integrate lightbox libraries like GLightbox for full-screen viewing, create masonry galleries with column-count, build image sliders with JavaScript navigation, and optimize performance with lazy loading attributes. Gallery fields eliminate gallery plugin dependencies while providing complete control over gallery styling, functionality, and responsive behavior.

  1. ACF Gallery Field Documentation
  2. GLightbox Library
  3. CSS Grid Layout Guide
  4. Native Lazy Loading
  5. Responsive Images

Call to Action

Gallery configurations need backup protection. Backup Copilot Pro backs up your WordPress gallery field settings and image data automatically. Safeguard your custom galleries—start your free 30-day trial today!