Skip to content

Keeping Advanced Custom Fields data when you move from WordPress to Laravel ​

On many WordPress sites, the content that matters most is not in the post body. It is in Advanced Custom Fields: a book's ISBN, a team member's photo, a page's hero section, a repeater of quotes, flexible content that builds a whole landing page.

ACF has its own way of storing all of that:

  • Field groups and fields are posts of type acf-field-group and acf-field, with their settings in post_content and their location rules ("show this group on books").
  • Values sit in postmeta, termmeta or usermeta under the field's name, with a reference beside each one: _{name} holds the field key.
  • A repeater keeps its row count under its name, and each row's values as {repeater}_{row}_{sub}. A group's sub fields are {group}_{sub}. Flexible content keeps a list of its row layouts.
  • Options pages keep their values in wp_options as options_{name}.

A usual Laravel rebuild turns this into columns or a JSON blob, so someone has to decode the repeater rows, guess the types and write an importer. The editors lose the forms they knew: field groups, location rules, conditional logic. And every template that called get_field() is rewritten against the new schema.

How kitloom reads it ​

kitloom runs on the WordPress database as it is. kitloom/custom-fields uses ACF's layout as its data model. It reads field groups and fields as the plugin stores them, with their location rules and conditional logic. It reads values under the same meta keys and writes them back in the same places. Data stored by Secure Custom Fields, the free fork, is read the same way.

All 37 field types are covered, PRO ones included:

CategoryTypes
Basictext, textarea, number, range, email, url, password
Contentimage, file, wysiwyg, oembed, gallery
Choiceselect, checkbox, radio, button_group, true_false, nav_menu
Relationallink, post_object, page_link, relationship, taxonomy, user
Advancedgoogle_map, date_picker, date_time_picker, time_picker, color_picker, icon_picker
Layoutmessage, accordion, tab, group, repeater, flexible_content, clone

In a theme or any Laravel code, a value is one call:

php
$post->field('subtitle');               // 'Under the title'
$post->field('slides');                 // [['caption' => 'One'], ['caption' => 'Two']]

use Kitloom\CustomFields\Facades\Fields;

Fields::option('footer_text');          // a value of the options screen

Values come back the way get_field($name, $post, false) returns them: ids stay ids, a repeater is a list of rows, a flexible content row carries acf_fc_layout. Writing goes through the same checks as the form. $post->setField('subtitle', 'Under the title') checks required fields, choices, ranges and conditional logic. Given a user, as in $post->setFields([...], $user), it also refuses a write that user may not make.

Post types and taxonomies made with ACF come along too. kitloom/post-types reads them from their acf-post-type and acf-taxonomy posts, so their archives and single pages answer at the same addresses.

What the admin shows ​

Both admins have a Custom Fields section with Field Groups and Add New Field Group, as in the plugin's menu. A group has its title, fields, location rules, style, label and instruction placement and order. Each field has a label, a name, a type with that type's settings, instructions, required and conditional logic.

  • Filament edits a group's fields on the group's form, nested a few levels, as the plugin's editor does. A record's form shows the matching groups in a Custom Fields block on post, term and user forms. A repeater is a Repeater, flexible content a Builder with a block per layout.
  • Nova edits fields one by one, because a single form holding every type's settings at every level would weigh megabytes in the browser. A Custom Fields tab on post and taxonomy forms shows the matching groups, with repeaters and flexible content as rows.

In both, image, file and gallery fields open the media library, and wysiwyg fields use the site's visual editor (TinyMCE). The screens need the manage_field_groups permission, which administrators have.

With kitloom/languages, text fields follow the page's language. With kitloom/ai, an agent reads a record's fields with their types and values (fields_get) and sets them (fields_set), checked as the form checks them and only where its user may edit the record.

What changes and what is not covered ​

  • The move is one way. Lists and maps kitloom writes are JSON. What ACF serialized is read as it is, never as PHP objects, and becomes JSON on its next save. ACF on WordPress would not read those values back.
  • Groups have to be in the database. kitloom reads field groups from their posts. A group that only ever lived in PHP (acf_add_local_field_group()) or in acf-json files is not there to read. GroupWriter takes the array ACF exports to JSON, so such a group can be saved once. Its values are in meta already.
  • Not here yet: the options pages screen (the values are stored and read already), ACF blocks, bidirectional relationships, and fields in REST output.
  • The plugin's Position setting (after the title, in the side column) has no counterpart on these forms. A group keeps the value it had.
  • Plugin code does not run. Templates that call get_field() are rewritten in a kitloom theme with $post->field(). There is no Gutenberg in the admin, and ACF blocks in content are not drawn yet.

Fill and fix fields with an AI agent ​

With kitloom/ai installed, an agent connected to the site knows the field groups before it touches anything: describe_site tells it which groups show on which post types. It reads a record's fields with their types (fields_get) and writes them (fields_set), checked exactly as the edit form checks them, so a wrong value is refused rather than stored. That turns field work that took an afternoon of clicking into one request:

  • "Fill the servings and preparation time fields of every recipe from its text."
  • "Find the projects whose client field is empty and tell me which they are."
  • "Copy the subtitle into the new summary field on every case study, shortened to one sentence."

The agent works as the user whose key it holds and may only change records that user may edit. Every change is in the activity log. How to connect one: Run your site from Claude or Cursor.

Check it on your own site ​

Create a MySQL user that can only read, point kitloom at the live database, and look at your pages. Then run compare-sites.php against both sites. It compares status codes, titles, content and lists address by address, and it flags block comments that reached kitloom's page undrawn, such as ACF blocks. How fields are laid out on a page is the theme's work, so look at your field-heavy templates yourself or let an AI agent go through them. Signing in to the admin needs a copy of the database. The steps are in Migrate from WordPress, and the details of storage are in the Custom Fields guide.

Try it ​

Open a sandbox in one click, with no sign-up: Filament or Nova. When it fits, see Pricing.

ACF rarely comes alone. The same approach covers WooCommerce, Contact Form 7 and Flamingo and Rank Math. An overview is in your plugin data after moving to Laravel.

Last updated:

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]