ACF shortcodes don’t work until post is saved again

When your ACF shortcodes aren't working until you manually update or save the CPT again, follow this guide to automate the process.

Introduction

I was recently generating some posts for a custom post type that includes ACF fields using WP All Import based on a CSV file I created.

The generation of the posts went fine, and the custom ACF fields seemed to be populated properly by WP All Import. There were some issues when trying to use the fields in my templates though.

Problem description

After importing my CSV using WP All Import, the posts were created and the ACF custom field was populated in the admin UI or backend.

I have an Elementor single page template which I use for this custom post type, where I want to use the ACF custom field in the “Title” and in the “Text Editor” elements.

The problem is that I could retrieve the ACF field for the “Title”, but not in the “Text Editor” when using the ACF shortcodes.

Elementor Heading element setting for ACF field
Elementor Heading element setting for ACF field
Elementor Text Editor element with ACF shortcode

The first way of retrieving the field works, but the shortcode one does not.

As you can see in the admin UI, the field does actually exist and is populated:

CPT with ACF field

Solution

You have two options here:

  • Save every post one by one manually through the admin UI
  • Loop over the posts through Python and update each one

I have over 300 posts, so I opted for automation through Python and the WordPress REST API.

Prerequisites

Make sure you have the option “Show in REST API” enabled for your ACF Field Group.

You will also need to create a password for the WordPress REST API under Users -> Profile -> Application Passwords. Remember this password for later.

Python code

I used Python and the WordPress REST API to solve this.

You can use the following code and adjust to your needs. You will have to change:

  • API URL
  • CPT in the URL
  • ACF fields

The way this script works is:

  1. Retrieve all posts of a certain custom post type
  2. For each post: get the ID and all the custom fields that -> this data is saved to variables
  3. For each post: update the post (based on the ID from earlier) with all the custom fields as data

You could probably just add a tag instead of reading/saving all the ACF fields again, but I haven’t tried this. You could also automate the retrieval of the ACF tags, but for me it was a one-time use, so I didn’t bother.

from tqdm import tqdm
import requests
from urllib3.exceptions import InsecureRequestWarning
import base64
import json


requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

# GLOBAL VARS
wordpress_user = "Your username which you created the application password for"
wordpress_password = "Your password (including the spaces)"
wordpress_credentials = wordpress_user + ":" + wordpress_password
wordpress_token = base64.b64encode(wordpress_credentials.encode())
wordpress_header = {'Authorization': 'Basic ' + wordpress_token.decode('utf-8')}


def page_numbers():
    """Infinite generate of page numbers"""
    num = 1
    while True:
        yield num
        num += 1


def get_posts():
    posts = []
    api_url = 'https://YOUR-DOMAIN.be/wp-json/wp/v2/YOUR-CPT-NAME'

    for page in tqdm(page_numbers()):
        # Fetch the next [pagesize=10] posts
        posts_page = requests.get(api_url, headers=wordpress_header, params={"page": page, "per_page": 100}, verify=False).json()
        # Check for "last page" error code
        if isinstance(posts_page, dict) and posts_page["code"] == "rest_post_invalid_page_number":  # Found last page
            break
        # No error code -> add posts
        posts += posts_page

    return posts


def save_post(posts):
    base_api_url = 'https://YOUR-DOMAIN.be/wp-json/wp/v2/YOUR-CPT-NAME/'

    # This will retrieve the populated ACF field first and save it to a variable
    # This data is then used to save it to the post again

    for post in tqdm(posts):
        # Get the needed ACF fields
        # Get ID and regio
        id = str(post['id'])
        regio = post['acf']['regio']
        # ADD YOUR ACF FIELDS HERE - DELETE REGIO

        # WP REST API data obj
        data = {
            "acf": {
                "regio": regio
                # ADD YOUR ACF FIELDS HERE - DELETE REGIO
            }
        }

        # Update CPT
        response = requests.post(base_api_url + id, headers=wordpress_header, json=data, verify=False)
        if response.status_code != 200:
            print("ERROR: API returned non 200")
            break


if __name__ == '__main__':
    # Get all posts for CPT
    posts = get_posts()
    # Save all posts with CPT data to fix bug in WP All Import
    save_post(posts)

Table of Contents