With ACF (SCF) image fields, the field’s “Return Value” setting (ID, URL or array) changes how you write your template. Copy and paste code without knowing that, and you end up with “Array” on the page or an empty alt attribute. The bottom line: set the return value to “Image ID” and hand it to wp_get_attachment_image()—that’s the easiest route, and it handles alt text and responsive images for you.
First check: the three “return values” of an image field
The field editing screen (“Field Groups” → the field in question) has a “Return Value” setting. What you choose there changes what get_field('photo') gives back.
| Return value setting | What get_field() returns | When it fits |
|---|---|---|
| Image ID | A number like 42 | Recommended. You want a function to build an img tag with alt and srcset |
| Image URL | https://example.com/wp-content/uploads/...jpg | You just need it in a src attribute or a CSS background |
| Image Array | An array holding url, alt, sizes and more | You want fine control over width, height, caption and so on |
None of them is “the correct one”—the trick is to pick the format that suits the code you’re about to write. When you take over an existing site, check the settings screen first to see which one it uses.
Recommended: receive an ID and pass it to wp_get_attachment_image()
Set the return value to Image ID and your template is essentially one line.
<?php echo wp_get_attachment_image(get_field('photo'), 'large'); ?>That alone outputs an img tag like this.
<img width="1024" height="683" src="…-1024x683.jpg" class="attachment-large size-large"
alt="A long-tailed tit perched on a snowy branch" loading="lazy" decoding="async"
srcset="…-300x200.jpg 300w, …-768x512.jpg 768w, …-1024x683.jpg 1024w" sizes="…">- alt attribute—the “alternative text” from the media library goes in automatically
- srcset / sizes—the right size for the screen width is chosen automatically (responsive)
- loading=“lazy”—loading is deferred until the image comes into view
Build the <img> yourself and you have to write all of that by hand. Receiving an ID is how you choose to let WordPress handle it.
To add a class, pass an array as the fourth argument.
<?php echo wp_get_attachment_image(get_field('photo'), 'large', false, ['class' => 'product-photo']); ?>When you receive a URL
If the return value is Image URL, you can print it directly with the_field(). This is the shortest form.
<img src="<?php the_field('photo'); ?>" alt="">But you’re now writing the alt text yourself, so if the content is predictable, either add another field for it or use the post title as below.
<img src="<?php echo esc_url(get_field('photo')); ?>" alt="<?php the_title_attribute(); ?>">The URL format is handy when you want a CSS background image.
<div class="hero" style="background-image: url('<?php echo esc_url(get_field('hero')); ?>');">When you receive an array
If the return value is an Image Array, receive it into a variable with get_field() and pull out the keys. Its strength is that width, height and caption are all available.
<?php $img = get_field('photo'); ?>
<?php if ($img) : ?>
<figure>
<img src="<?php echo esc_url($img['sizes']['medium']); ?>"
width="<?php echo esc_attr($img['sizes']['medium-width']); ?>"
height="<?php echo esc_attr($img['sizes']['medium-height']); ?>"
alt="<?php echo esc_attr($img['alt']); ?>">
<?php if ($img['caption']) : ?>
<figcaption><?php echo esc_html($img['caption']); ?></figcaption>
<?php endif; ?>
</figure>
<?php endif; ?>These four keys are the ones you’ll use most.
$img['url']—the URL of the original size$img['alt']—the alternative text$img['caption']—the caption$img['sizes']['medium']—the URL of the medium size (thumbnail,largeand others follow the same shape)
Even when you receive an array, passing $img['ID'] to wp_get_attachment_image() still builds a tag with srcset. “Details from the array, tag generation from the function” is a handy combination.
Keeping the layout intact when there’s no image
An image field can be empty—and a template that doesn’t account for that breaks its layout on any post where the image was forgotten. Wrapping it in an if is all it takes.
<?php if ($id = get_field('photo')) : ?>
<?php echo wp_get_attachment_image($id, 'large'); ?>
<?php else : ?>
<img src="<?php echo esc_url(get_template_directory_uri() . '/images/no-image.png'); ?>" alt="">
<?php endif; ?>$id = get_field('photo') is the “assign it and test whether it’s empty at the same time” idiom. It fits on one line, so you’ll see it a lot.
Images inside a repeater field
Inside a repeater you use get_sub_field(), not get_field(). This one is easy to get wrong.
<?php if (have_rows('gallery')) : ?>
<ul class="gallery">
<?php while (have_rows('gallery')) : the_row(); ?>
<li><?php echo wp_get_attachment_image(get_sub_field('image'), 'medium'); ?></li>
<?php endwhile; ?>
</ul>
<?php endif; ?>TipsHow to output an ACF repeater fieldWant the details on outputting repeater fields? Here you goSummary
- Image fields change how you write code depending on the return value (ID, URL, array)—check the settings screen first
- Receiving an ID and using
wp_get_attachment_image()is the shortest path. Alt, srcset and lazy loading all come along automatically - URL suits
srcand background images; the array suits captions, widths and heights - Always include an
iffor the empty case. Inside a repeater, useget_sub_field()
Images carry a lot of visual weight, so cutting corners on alt text and sizes shows up directly as a difference in quality. Let the functions do the parts they can.