Skip to content

From Magic Fields 1.x to ACF, Part 1

Other posts in this series: Part 1 (this post); Part 2; Part 3; Part 4; Part 5; Part 6; Part 7; Part 8; Part 9

A long-needed migration


For a site I manage,, I’ve been using the Magic Fields plugin for more than four years. The 1.x version I use was superseded by a version 2.x, but that was totally incompatible with 1.x. So I’ve been kind of rumbling along on 1.x, no longer actively maintained, and always meaning to make a change over to something different.

Magic Fields does more than create sets of custom fields. It also creates custom post types. Maybe that’s the magic part, because it does two things at once. You still have to manage your own child themes and templates, but the two major pieces of the puzzle are there already. So in addition to the fields, I need a system for creating and managing post types as well.

At first, I looked into creating them manually in a child theme and putting them into a functions.php file. Besides creating a cumbersome functions file, it made it so that the post types weren’t going to be portable. If I needed to switch out themes at some point in the future, for debugging, or a redesign, it would take extra work to copy them over.

After considering a couple of options, including Pods and Advanced Custom Fields (ACF), I decided to go with a combination of ACF and Custom Post Type UI (CPT UI).

My site was built from four Magic Fields “Write Panels.” MF uses these to group together custom fields for a given type of post. My four are tailored for a community orchestra’s performance calendar. We have

  • Seasons; which are built up of …
  • Programs; which contain …
  • Musical Works; and …
  • Artists.

Two of the Write Panels are fairly simple, with simple non-grouped fields and no related types. For example, a Musical Work consists of a:

  • Title
  • Subtitle
  • Composer
  • Arranger
  • Composed
  • Program Notes
  • MP3
  • Copyright

Not all of them are mandatory of course, but you have to have at least a Title and a Composer. Each of them is either a textbox, a multiline textbox, or a dropdown list (for the composers).

But the idea is to generate and display useful and informative fields, like this:

In this episode we’ll study the similarities and differences of MF and ACF, and start doing a transformation of the data.

But First A Strange Interlude

Some random notes on what some of the MF tables that are clogging up your database are about.

  • mf_custom_field_options. In my case, there is only one entry here and that is the options in the composer select box.
  • mf_custom_field_properties. This seems to have mostly to do with default image sizes and date formats.
  • mf_module_groups: Information about the field groups, and specifically the panels.
  • mf_panel_category: Maps panels to WordPress post categories.
  • mf_panel_custom_field: Groups together information about the specific custom fields.
  • mf_panel_standard_field: Non-custom fields.
  • mf_posttypes_taxonomies: In my case, this table is empty.
  • mf_post_meta: Group counts, field counts, post_ids, field_names, and ordering.
  • mf_write_panels: The gist of MF, these are the top-level groupings of custom fields. In this case:
    1. musical_work
    2. performance
    3. performer
    4. seasons

Work on a copy of your database! If you blow it up I don’t want to hear about it.

Your Existing Data

Let start with the structure of your existing MF Write Panel. Here are the fields as they show up on the dashboard.

The Magic Fields Write Panel for Musical Works.

The “name” column is something to make a note of. It’s the WordPress sluggified version of the label. We’ll be needing these for ACF.

These are all going to show up in your database as part of the postmeta table. For example, pick your post ID (for the purposes of this exercise I’m using post_id 7, which is the Beethoven Coriolanus Overture. Run the query:

SELECT * FROM postmeta WHERE post_id=7
And among the rows returned are …
subtitle="Op. 62",
composer="Ludwig van Beethoven",

So these meta_keys (subtitle, composer etc.) are exactly the same as the MF Write Panel “name” of the field. This is going to be really useful.

Setting up Advanced Custom Fields

This is where backing up your data comes in. In fact, I suggest you clone your entire site and work on that. I used the Duplicator plugin, set up a subdomain on my server for, and experimented on that. Once it works the way I want I’ll simply switch the subdomain mappings over.

ACF calls its equivalent of Write Panels “Field Groups.” To get started, de-activate Magic Fields, then install and activate ACF. You’ll see the menu item for Custom Fields, then Field Groups. Add a new one, and you can set up your fields using the exact same naming conventions you used with Magic Fields.

The Musical Work Field Group in ACF.

Just make them all the same Name (and you might as well use the same Label too unless you’ve got a good reason), and the same type. Don’t worry too much about the select box: You can just copy/paste the contents from the MF edit area into the ACF edit area — it’s just a running text list. ACF stores it as a serialized array in a post type of type “acf-field”; MF stores it in a table, “mf_custom_field_options”.

Custom Post Type UI

Next, install and activate the Custom Post Type UI plugin.

When you first start in on this, the number of options are kind of overwhelming. But for most purposes, the Labels section is just a lot of typing and the defaults are good enough. You might want to use a Post Type Slug that’s similar to what you’ve been using, just for sanity’s sake. Labels are for you, not the system. And auto-populating is fine.

But do pay attention in the settings section. If you’re working from a Magic Fields installation you want all of this to be public, queryable, shown in the nav and admin menus, not deleted and so on. You can add a menu icon if you want — this is just for you and your other authors. But make sure that it supports — and here’s where the defaults aren’t enough — Custom Fields. Otherwise why are you reading this? You can decide on your own about things like excerpts, featured images, comments. You’ll probably want to include some taxonomies like categories and tags.

Once you’ve got these going by the way they’re easy to export. It will give you the code you need if you do decide to go via the functions.php route. Here’s what one looks like.

function cptui_register_my_cpts_musical_work() {
* Post Type: Works.
$labels = [
"name" => __( "Works", "custom-post-type-ui" ),
"singular_name" => __( "Work", "custom-post-type-ui" ),
"menu_name" => __( "Works", "custom-post-type-ui" ),
"all_items" => __( "All Works", "custom-post-type-ui" ),
"add_new" => __( "Add new", "custom-post-type-ui" ),
"add_new_item" => __( "Add new Work", "custom-post-type-ui" ),
"edit_item" => __( "Edit Work", "custom-post-type-ui" ),
"new_item" => __( "New Work", "custom-post-type-ui" ),
"view_item" => __( "View Work", "custom-post-type-ui" ),
"view_items" => __( "View Works", "custom-post-type-ui" ),
"search_items" => __( "Search Works", "custom-post-type-ui" ),
"not_found" => __( "No Works found", "custom-post-type-ui" ),
"not_found_in_trash" => __( "No Works found in trash", "custom-post-type-ui" ),
"parent" => __( "Parent Work:", "custom-post-type-ui" ),
"featured_image" => __( "Featured image for this Work", "custom-post-type-ui" ),
"set_featured_image" => __( "Set featured image for this Work", "custom-post-type-ui" ),
"remove_featured_image" => __( "Remove featured image for this Work", "custom-post-type-ui" ),
"use_featured_image" => __( "Use as featured image for this Work", "custom-post-type-ui" ),
"archives" => __( "Work archives", "custom-post-type-ui" ),
"insert_into_item" => __( "Insert into Work", "custom-post-type-ui" ),
"uploaded_to_this_item" => __( "Upload to this Work", "custom-post-type-ui" ),
"filter_items_list" => __( "Filter Works list", "custom-post-type-ui" ),
"items_list_navigation" => __( "Works list navigation", "custom-post-type-ui" ),
"items_list" => __( "Works list", "custom-post-type-ui" ),
"attributes" => __( "Works attributes", "custom-post-type-ui" ),
"name_admin_bar" => __( "Work", "custom-post-type-ui" ),
"item_published" => __( "Work published", "custom-post-type-ui" ),
"item_published_privately" => __( "Work published privately.", "custom-post-type-ui" ),
"item_reverted_to_draft" => __( "Work reverted to draft.", "custom-post-type-ui" ),
"item_scheduled" => __( "Work scheduled", "custom-post-type-ui" ),
"item_updated" => __( "Work updated.", "custom-post-type-ui" ),
"parent_item_colon" => __( "Parent Work:", "custom-post-type-ui" ),
$args = [
"label" => __( "Works", "custom-post-type-ui" ),
"labels" => $labels,
"description" => "",
"public" => true,
"publicly_queryable" => true,
"show_ui" => true,
"show_in_rest" => true,
"rest_base" => "",
"rest_controller_class" => "WP_REST_Posts_Controller",
"has_archive" => false,
"show_in_menu" => true,
"show_in_nav_menus" => true,
"delete_with_user" => false,
"exclude_from_search" => false,
"capability_type" => "post",
"map_meta_cap" => true,
"hierarchical" => true,
"rewrite" => [ "slug" => "musical_work", "with_front" => true ],
"query_var" => true,
"menu_position" => 5,
"supports" => [ "title", "editor", "thumbnail", "excerpt", "custom-fields", "comments", "revisions", "page-attributes", "post-formats" ],
"taxonomies" => [ "category", "post_tag" ],
register_post_type( "musical_work", $args );
add_action( 'init', 'cptui_register_my_cpts_musical_work' );

I know, tl;dr. But this is what it saves you.

I’ve set up my four post types:

  • musical_work
  • performer
  • program
  • season

And by using these “sluggified” names I can start to develop my templates.

The key thing to remember is that it’s really simple. Follow your usual procedure for making a child theme. You can use a plugin, or just set up a new directory in your ‘themes’ folder with an appropriate style.css and functions.php file. I won’t go into it here, there are some easy tutorials out there.

But then within your child theme, take the parent theme’s “single.php” file, copy it into your new folder once for each custom post type, and name them:

  • single-musical_work.php
  • single-performer.php
  • single-program.php
  • single-season.php

In other words, “single-” followed by “post_type” followed by “.php”.

And then, within each of these files, add code to display the appropriate custom fields. If you’re using Magic Fields, you’ve already got something like this in your current child theme. It might look like this:

/* New section to account for Magic Fields */
//musical work section
//if there's a composer...
if ( get('composer') ) {
echo '<p>';
echo '<strong>' . get('title');
if ( get('subtitle') ) echo '<br />' . get('subtitle');
echo '</strong><br />';
echo 'By ' . get('composer') ;
if ( get('arranger') ) echo '<br />Arranged by ' . get('arranger');
if ( get('composed') ) echo '<br />Composed in ' . get('composed');
echo '</p>';

Magic Fields’ preferred parlance is the get() function. For ACF it will look like this:

// Callback to render the work information from ACF
// vars
$composer = get_field('composer');
$title = get_field('title');
$subtitle = get_field('subtitle');
$composer = get_field('composer');
$arranger = get_field('arranger');
$composed = get_field('composed');
$mp3 = get_field('mp3');
$copyright= get_field('copyright');
<div class="info_block">
<?php if ( '' != $subtitle ) echo "<br>$subtitle"; ?>
<?php if ( '' != $composed ) echo "<br>Composed in $composed"; ?>

By <strong><?php echo $composer; ?></strong>
<?php if ( '' != $arranger ) echo "<br>Arranged by $arranger"; ?>
<?php if ( '' != $copyright ) echo "<br>© $copyright"; ?>
<!-- MP3 player? -->
if ( '' != $mp3 ) {
$mp3 = preg_replace('/[^@]*@(.*)/', "$1", $mp3);
echo '<audio controls>';
echo '<source src="' . $mp3 . '" type="audo/mp3">';
echo '</audio>';

ACF uses get_field() to do the work of get();


To change these “panel” posts to the new custom post type, it might be tempting to just go with a category selection (looking in the postmeta for a category match for a performer or a musical work). But more precise would be to use the magic field panel that was set up for each. As noted earlier you’ll find them in mf_write_panels
id, name, etc.
1 musical_work
2 performance
3 performer
4 seasons

With all that said, for musical works and performers, all we might need is to change the content type, at least to start. The mf_write_panel_id for musical works is id 1. I’m going to copy the posts table to a backup first, and then run this query in phpMyAdmin:

UPDATE `prefix_posts` SET post_type='musical_work' WHERE ID IN (
SELECT ID FROM `prefix_posts`
LEFT JOIN `prefix_postmeta` ON `prefix_posts`.ID=prefix_postmeta.post_id
WHERE meta_key = '_mf_write_panel_id' AND meta_value = 1)
as t )

The query is a little tricky — notice the two nested SELECT statements. That’s because you can’t directly modify a table you’re running a subquery on. But a subquery of a subquery, for whatever reason, works

At which point all 222 of my musical works have been transformed. A quick spot check shows that they all display properly using the new custom post type!

And again. The mf_write_panel_id for performers is 3:

UPDATE `prefix_posts` SET post_type='performer' WHERE ID IN (
SELECT ID FROM `prefix_posts`
LEFT JOIN `prefix_postmeta` ON `prefix_posts`.ID=prefix_postmeta.post_id
WHERE meta_key = '_mf_write_panel_id' AND meta_value = 3)
as t )

Sets all the performers. And a spot check confirms it!

That’s the two easy ones. In the next chapter I’ll examine how to create a program out of musical_works and performers. These call upon related or linked posts, and they behave very differently from Magic Fields to Advanced Custom Fields. So stay tuned.

One Comment

  1. Tom Tom

    I made an error in setting up the custom post types. They should all have an archive enabled, since I do in fact put these into the nav menus.

Leave a Reply

Your email address will not be published. Required fields are marked *

Share This