How to Use Data from Advanced Custom Fields to Generate Social Media Visuals in WordPress
Use ACF fields in WordPress to build social graphics or link previews, expose data safely through REST, and automate captures with ScreenshotNeo.

Short answer: Advanced Custom Fields (ACF) supplies structured values such as a headline, summary, author, category, or image reference. Read those values in a WordPress template or expose them through the WordPress REST API, place them into an image layout, then publish either a downloadable graphic or an Open Graph image for link previews. ACF stores and exposes the data; it does not automatically render finished social artwork.
There are two related workflows:
- Generated asset: create a PNG, JPEG, or WebP file that someone uploads directly to a social network.
- Link preview: keep the graphic at a public URL and emit
og:imageon the WordPress page so a social crawler can display it when the page is shared.
This guide shows both paths, including field design, PHP templates, REST API retrieval, image IDs, security, Open Graph metadata, automation, troubleshooting, and a browser-free option with ScreenshotNeo.
1. Model the ACF fields for a visual
Create a field group for the post type that will produce the visual. A practical set of fields is:
| Field | Type | Purpose |
|---|---|---|
| Social headline | Text | Short title that fits the design |
| Social summary | Textarea | Supporting copy or excerpt |
| Brand image | Image | Logo, author portrait, or background asset |
| Share image override | Image | Optional per-post image |
| Theme | Select | Controls a light or dark visual variant |
These names are implementation choices. The important ACF behavior is the image field return format. ACF can return an image array, an image URL, or an attachment ID; your code must match the selected format. See the ACF Image field documentation.
2. Read fields in a server-rendered WordPress template
For a traditional theme, ACF documents get_field() for retrieving a value and the_field() for printing one. The following template reads a text value and safely handles all three image return formats.
<?php
$headline = get_field('social_headline');
$summary = get_field('social_summary');
$image = get_field('share_image_override');
$image_url = '';
$image_alt = '';
if (is_array($image)) {
$image_url = $image['url'] ?? '';
$image_alt = $image['alt'] ?? '';
} elseif (is_numeric($image)) {
$image_id = (int) $image;
$image_url = wp_get_attachment_image_url($image_id, 'full');
$image_alt = get_post_meta($image_id, '_wp_attachment_image_alt', true);
} elseif (is_string($image)) {
$image_url = $image;
}
if (!$image_url) {
$image_url = get_template_directory_uri() . '/assets/default-share.jpg';
}
?>
<article class='social-card theme-'>
<img src='<?php echo esc_url($image_url); ?>'
alt='<?php echo esc_attr($image_alt); ?>'>
<h1><?php echo esc_html($headline); ?></h1>
<p><?php echo esc_html($summary); ?></p>
</article>
Escape text with esc_html(), URLs with esc_url(), and attribute values with esc_attr(). If the field returns an attachment ID, wp_get_attachment_image_url() resolves the file URL. For responsive HTML output, WordPress can render an attachment with wp_get_attachment_image(), which also supplies responsive srcset markup.
3. Expose only the required fields through the REST API
ACF field groups are not visible in the WordPress REST API by default. Enable Show in REST API in the field group settings. ACF states that REST integration has been available since version 5.11, and exposed values appear under an acf object in the post response.

Do not expose every editorial field simply because an automation needs one headline and one image. ACF’s security guidance explains that stored values are private until you deliberately make them available through REST. Keep write operations authenticated.
Inspect a post and its schema
curl 'https://example.com/wp-json/wp/v2/posts/123?context=view'
curl -X OPTIONS 'https://example.com/wp-json/wp/v2/posts'
The exact response shape depends on your WordPress and ACF versions. A basic image return format may be an attachment ID. If you need richer formatted values, ACF documents the acf_format=standard query parameter:
curl 'https://example.com/wp-json/wp/v2/posts/123?acf_format=standard'
Check the live response and schema rather than assuming an image will always be an array.
Resolve an attachment ID
When the ACF image value is an ID, use the WordPress media endpoint to retrieve attachment information:
curl 'https://example.com/wp-json/wp/v2/media/456'
The media resource can provide the source URL, alternate text, dimensions, and other attachment metadata. The WordPress Media REST API reference documents the endpoint.
4. Retrieve ACF data from Python or Node.js
A decoupled generator can fetch the post, read acf, then resolve the image attachment. These examples use only standard libraries plus the commonly used HTTP clients.
Python
import requests
base = 'https://example.com/wp-json/wp/v2'
post = requests.get(f'{base}/posts/123?acf_format=standard', timeout=30)
post.raise_for_status()
data = post.json()
fields = data.get('acf', {})
headline = fields.get('social_headline', '')
image_value = fields.get('share_image_override')
if isinstance(image_value, int):
media = requests.get(f'{base}/media/{image_value}', timeout=30)
media.raise_for_status()
image_url = media.json().get('source_url')
else:
image_url = image_value.get('url') if isinstance(image_value, dict) else image_value
print(headline)
print(image_url)
Node.js
const base = 'https://example.com/wp-json/wp/v2';
const post = await fetch(`${base}/posts/123?acf_format=standard`);
if (!post.ok) throw new Error(`Post request failed: ${post.status}`);
const data = await post.json();
const fields = data.acf || {};
const imageValue = fields.share_image_override;
let imageUrl;
if (typeof imageValue === 'number') {
const media = await fetch(`${base}/media/${imageValue}`);
if (!media.ok) throw new Error(`Media request failed: ${media.status}`);
imageUrl = (await media.json()).source_url;
} else if (imageValue && typeof imageValue === 'object') {
imageUrl = imageValue.url;
} else {
imageUrl = imageValue;
}
console.log(fields.social_headline, imageUrl);
5. Turn the data into a graphic
ACF and WordPress provide the content and asset references. A separate rendering step must turn those values into pixels. One maintainable pattern is to render an HTML/CSS card using the fetched fields, then capture that page at a fixed viewport. Keep text length bounded, define a fallback image, and choose a deterministic font stack so repeated renders are stable.
For an uploadable asset, save the resulting PNG, JPEG, or WebP to your media storage and attach it to the post. For a link preview, the rendered image only needs a stable public URL; you do not have to replace the post’s featured image unless that is part of your editorial workflow.
Handle long or missing values
- Use a fallback headline such as the WordPress post title when the ACF field is empty.
- Clamp summaries by characters or lines before rendering.
- Use a default background when an image field is unset.
- Normalize line breaks and strip unsafe HTML if a field is intended to be plain text.
- Keep the final image URL publicly reachable by the social crawler, preferably over HTTPS.
6. Publish the image as an Open Graph preview
A generated file and a shared-link preview are separate deliverables. For a preview, the page must emit Open Graph metadata. The protocol uses og:image for the image URL representing the page and supports structured properties including secure URL, MIME type, width, height, and alt text. The Open Graph specification says a page specifying og:image should also specify og:image:alt.
<meta property='og:image' content='https://cdn.example.com/social/post-123.webp'>
<meta property='og:image:secure_url' content='https://cdn.example.com/social/post-123.webp'>
<meta property='og:image:type' content='image/webp'>
<meta property='og:image:width' content='1200'>
<meta property='og:image:height' content='627'>
<meta property='og:image:alt' content='<?php echo esc_attr($headline); ?>'>
If an SEO or social plugin already emits Open Graph tags, configure it or hook into its output instead of adding a second competing set. Duplicate tags can make debugging image selection difficult.
LinkedIn’s documented sharing guidance requires Open Graph and lists a minimum sharing image size of 1200 × 627 pixels. Treat that as LinkedIn-specific guidance; confirm current dimensions, formats, and limits for every other network you target.
7. Automate the capture with ScreenshotNeo
Once your WordPress endpoint renders a visual page, ScreenshotNeo can capture it without maintaining a browser worker. It supports full-page or element capture, custom CSS and JavaScript, waiting for a selector or network idle, custom headers and cookies, resizing, caching, and HTML/CSS-to-image workflows.

cURL:
curl -G 'https://api.screenshotneo.com/v1/shot' \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/social-card/123 \
-o social-card.webp
Python:
import requests
r = requests.get(
'https://api.screenshotneo.com/v1/shot',
params={
'access_key': 'YOUR_API_KEY',
'url': 'https://example.com/social-card/123'
},
timeout=90
)
r.raise_for_status()
open('social-card.webp', 'wb').write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/social-card/123'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('social-card.webp', buffer));
See the ScreenshotNeo documentation for the full parameter list and response behavior. Its response includes X-Page-Verdict and X-Billed headers, so a job can distinguish a clean capture from a bot check, blank page, timeout, failed load, or cache hit.
8. Or skip the browser setup
ScreenshotNeo accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with the WordPress visual endpoint.
9. Authentication and security checklist
- Expose only the ACF fields required by the generator.
- Never put WordPress Application Passwords, JWT secrets, or ScreenshotNeo access keys in browser JavaScript.
- Use authenticated requests for private posts and write operations. ACF’s REST integration follows WordPress core authentication methods.
- Validate post status and permissions before generating a draft or private post visual.
- Use HTTPS for WordPress, image storage, and callback endpoints.
- Keep generated URLs unguessable if the artwork contains embargoed content.
10. Troubleshooting
The REST response has no acf object
Cause: the field group is not set to Show in REST API, the post type is not exposed, or the request is hitting a different site or endpoint. Fix: enable REST visibility, confirm the post type endpoint, and inspect the response with an OPTIONS request.
The image value is the wrong shape
Cause: ACF’s image return format is configured as ID, URL, or array. Fix: branch on the value type, or standardize the field setting and update the consumer. Resolve IDs through /wp-json/wp/v2/media/{id}.
The preview shows an old image
Cause: the social crawler or your own cache retained the previous URL. Fix: publish a new versioned image URL, purge your CDN, and re-check the platform’s official debugger or sharing tool.
The card is blank or cropped
Cause: capture happened before fonts or images loaded, the selector did not exist, or the viewport does not match the design. Fix: wait for a selector or network idle, use a fixed viewport, provide fallback colors, and capture the card element rather than the entire page.
Private content appears in a public image
Cause: a public capture URL can access a page that should not be exposed, or the generated file was uploaded to public storage. Fix: require authentication for private routes, remove sensitive fields from public REST output, and use access-controlled storage.
11. Performance, reliability, and cost
- Cache by content version: include the post revision or a hash of the ACF values in the visual URL so unchanged posts reuse the same asset.
- Batch work: generate visuals asynchronously after publish rather than blocking the editor request.
- Limit payloads: request only the post and media resources needed; avoid exposing large repeater fields to every client.
- Retry carefully: retry transient HTTP failures with backoff, but do not duplicate media attachments when a previous request may have succeeded.
- Observe outcomes: record the post ID, template version, image URL, response status, and capture verdict. ScreenshotNeo’s verdict and billing headers help separate clean shots from non-billable failures and cache hits.
- Control spend: choose a cache TTL, avoid recapturing unchanged posts, and use bulk capture for up to 100 URLs per call when producing a batch.
12. FAQ
Does ACF generate the social image by itself?
No. ACF stores and exposes structured values. Your template, renderer, or capture service creates the final pixels.
Should I use an image URL or attachment ID?
Use whichever fits your workflow. URLs are convenient for rendering; IDs are useful when you need WordPress media metadata and can resolve them through the media endpoint.
Can I use the same visual for uploads and link previews?
Usually, yes. Save the generated file at a public URL for og:image, and use that same file for direct uploads when its dimensions and format meet the target network’s requirements.
Are ACF fields public after I enable REST?
Fields in an exposed group can be returned by the endpoint according to WordPress permissions and context. Review the response and publish only values intended for that audience.
What size should every social image be?
There is no universal size in the reviewed sources. LinkedIn documents a 1200 × 627 pixel minimum for sharing images; verify the current requirements of each network you support.


