I’m in the process of migrating a WordPress site I built in 2017 (i.e., pre-Gutenberg), into a new site with a Block Theme. The original site employs Advanced Custom Fields (ACF) extensively for functionality. It also uses a bespoke theme built with the StudioPress Genesis Framework and .php template files.
The new site will also use ACF; however, it uses a Block Theme on a multisite network. The new site’s template files are in .html so I don’t have the ability of adding custom .php templates.
While I can’t make custom .php templates, my organization maintains a “Custom Functionality Plugin” that is installed network-wide. Using such a plugin allows us to add functionality to sites that is theme independent (i.e., we can switch themes and maintain functionality).
Site Migration
Site migration is fairly straight forward with a little preparation. The trick is to set up ACF before you migrate the content.
ACF
In ACF, fields and groups can be exported as .json files. (Had I originally used ACF to create the CPT, its registration info could have been exported as .json too.)
Prior to migrating the content from the old site, I first exported and imported my ACF fields and groups into the new site.
Custom Post Type
As mentioned above, the original CPT was registered via a plugin using the register_post_type() function. I won’t be using this plugin on the new site; however, in addition to custom meta fields, CPTs can also be registered in ACF.
So prior to migrating the content, I registered a CPT on the new site via ACF using the exact sameslug and labels that I used when I originally registered the CPT via the function. Doing so will enable the original CPT SQL data to be imported with the rest of the content.
Content Migration
There is a lot of content on the original site. Its Media Library has been growing since it was built in 2017. I do not have access to the host on the new site’s network so I could not simply Rsync or FTP my wp-content directory from the old site to the new.
My approach was to use WordPress’ default Tools->Export feature on the original site to create an XML file and use the Tools->Import feature on the new site to import it, being sure to select the Download and import file attachments option.
This worked okay. The new site is on a multisite network with shared hosting. After a few minutes with the import it would choke and issue a 506 Error. I could tell that the import was working, however, because after the “failure,” I’d check the Media Library and could see that additional media items had been added. Re-running the import would gradually add more items (until it choked again). In order to get all my content into the new site, I had to run the import 28 times. But with perseverance, I was eventually able to get all my content and media imported into the new site.
In order to get all my content into the new site, I had to run the import 28 times.
ACF Challenge
After the migration, I was able to go into the editor of my CPTs on the new site and see my ACF fields and their content in the Post editor. The challenge now is getting that content to display on the front-end of the site using the Block Editor.
wp_postmeta vs post_content
ACF data is metadata — it is stored as separate entries in the wp_postmeta tables of the database; whereas block data is stored as part of post_content. As mentioned, the original site was a complete custom build. I relied on custom templates containing custom loops to render my custom fields on the front-end of the site. I don’t have the ability to create custom PHP templates in the new site so I need a way to get this content onto the page.
Post metadata vs Post content
Failed Solution — ACF Block
ACF Pro provides a PHP based framework for creating custom blocks for their fields. My first thought was that I needed to create a new custom block for my ACF field group and use that block to display my ACF content.
While I don’t have access to the new site’s server and I can’t make custom php templates, my organization maintains a “Custom Functionality Plugin” that is installed network-wide. Using such a plugin allows us to add functionality to sites that is theme independent (i.e., we can switch themes and maintain functionality).
In a development branch of this plugin in a local development environment, I followed a tutorial for creating an ACF Block and was, indeed, able to create a block that displayed my fields. However, the resulting block only allowed creating new entries with those fields; I was unable to display the entries that I imported from the original site via the block.
If I was building this site “from scratch,” creating an ACF Block is the approach I would consider, but it would not work in this use case.
ACF Block
Failed Solution — WordPress Block Bindings API
WordPress 6.5 introduced a new feature called the Block Bindings API, a new way of extending blocks that enable a developer to populate a block with a particular source. For instance, rather than creating a custom block to display metadata content as in the above example, you can bind a custom metadata field to a block and use, say, the Paragraph block to output your field content.
I’m very excited about the possibilities of using the Block Bindings API going forward; however, so far it only works on ACF’s basic string fields such as Text, Email, Number, or URL. It does not work for complex fields such as the Repeater or Flexible Content fields.
Moreover, as of this writing, in order to use Block Bindings with ACF, one needs to use the register_meta() function with 'show_in_rest' => true, with their field name in addition to creating it in ACF. I suspect ACF will update their plugin in the future so this additional step is not required.
I decided that a shortcode would be the best way to display my field data. The WordPress Gutenberg editor provides a Shortcode block, so a properly developed shortcode (or two) should do the trick.
As mentioned above, the original bespoke theme used custom loops in custom page and post templates to render field data on the front-end. For the new site, I converted the custom loops into shortcodes.
The Original Template Loop
The original site’s theme was developed using the StudioPress Genesis Framework. The original theme’s code below is from the CPT’s “single.php” template. It removes the default genesis_loop and replaces it with the bb_a_z_style_guide_single_loop() function, which returns the ACF field data.
Converting this to a shortcode is not to too difficult. Since I imported my ACF field definitions from the original site, the names in field calls are exactly the same. In the shortcode, I stripped out all the structural html code, as it’s no longer necessary.
return, don’t echo
An important thing to remember when writing shortcodes in PHP is that they are returned not echoed. So, all the echo statements in the above function need to be converted to a return. To do so, I set up an empty string variable at the beginning that I call $finaldefs. I build on it using .= concatenation and the $finaldefs variable is what is ultimately returned.
In the following example, the resulting shortcode is called [style-definition].
Now that I’ve converted my loop functions to shortcodes and added them to my custom plugin, I need to incorporate them into the new site and theme.
Single posts
One approach would be to put the Shortcode Block containing the [style-definition] shortcode at the top of all our custom posts. This would work, but there is too much repetition (it is not very DRY).
My approach is to use the WordPress Full Site Editor to create a new “Single Posts” template for my CPT.
Remove the Content Block
The default single template has a Content Block that displays all content from a post or page’s block editor. Because ACF data is not stored in post_content, the Content Block will not work for displaying our ACF Data. So, in my CPT single post template I replaced the Content Block with the Shortcode Block and dropped my [style-definition] shortcode into it.
Default Single Post/Page template with Content BlockCPT Single Post template with Shortcode Block and [style-definitions] shortcode
NOTE
Removing the Content Block from the Template will not remove the Content Editor from the single-post editor in your CPT. As the first image in this post illustrates, the Content Editor will appear above the ACF Field Group.
To minimize confusion, it might be advisable to disable the Content Editor altogether for the Style Guide CPT.
A function such as the following will disable the editor altogether in the CPT, leaving only the ACF Field Group
Now, with my CPT Single Template configured, my ACF content shows on the front-end of each post and I can edit the entries in the ACF Field Group area of the editor:
Front-end Style Guide entry with ACF content rendered by shortcode
Drawbacks
The most significant drawback of this approach is that the <html> and CSS are rendered by the shortcode. The wonderful controls of the Block Editor are not available for this solution.
Additionally, the plugin I have available for this is a network-wide plugin and even though this solution has little overhead in terms of code, would only be relevant to a single site within that network.
Going the ACF Block route would present me with the same issue as the shortcode, i.e., developing a custom block for a single site in a network-wide plugin.
But as mentioned, the Block Bindings API only works with and handful of blocks at the moment. Future versions of WordPress are sure to expand it to more blocks. I would like to see it expanded to, perhaps, the Query Loop Block, and see it incorporated with repeater fields.
In the meantime, however, the custom shortcode route is the approach I am taking. I’d love to hear anyone else’s ideas or solutions. If you either, please leave a comment!