Cybo Advanced Field EngineCyboweb Technologies · Documentation · v1.0.2

ACF → CAFE

CAFE → Migration → ACF → CAFE converts this site from Advanced Custom Fields (ACF, ACF PRO or Secure Custom Fields) to CAFE. It is a conversion of the site's fields, not a content migration:

  • every value stays on its own object: ACF on post 125 becomes CAFE on post 125;
  • no post, page, custom post type item, media item, term or user is created, changed or deleted;
  • ACF data stays untouched until you remove it yourself, at the very end, after every check passed.
ACF ── 1. convert ──▶ CAFE ── 2. verify ──▶ everything identical?
                                            ├─ no  → ACF stays untouched, fix and verify again
                                            └─ yes → 3. you confirm → remove ACF data

The eleven steps

Each step shows its real status. The server works it out from facts (the runs, the verification report, the code scan) and sends it with every response; the screen only adds the spinner while you wait for an action.

Status (screen) status (REST) Means
Waiting pending Not done yet
Running running Working: long steps show a progress bar (dry run, import, verification, removal)
Done done Finished and still valid
Needs attention warning Finished, but look at it: unsupported fields (3), ACF code left (9), verification older than the last import or not covering every object step 4 found (8), final verification expired (10), ACF data restored (11)
Failed failed ACF missing (1), fields missing (3), invalid values (6, 7), mismatches (8), final check failed (10)
Locked locked Step 11 until the final verification passed
# Step (key) Button What happens Writes
1 Detect ACF (detect) - ACF (or Secure Custom Fields) must be active; name and version are shown. Without it nothing starts. -
2 Field groups (groups) - The ACF field groups, through ACF's API (including groups from PHP and JSON files). -
3 CAFE fields (fields) Check, Create CAFE fields Check is a dry run. When CAFE refuses a field group, nothing is created and the step shows Failed with CAFE's reason per group. Every supported ACF field becomes the CAFE field with the same key, through CAFE's field group importer. Running it again changes nothing. CAFE field groups
4 Existing objects (objects) Find objects Counts the posts (all types, including media), terms, users and options pages that hold ACF values, and prepares one migration per kind. Migration settings
5 Values (values) Dry run Reads and converts every value, writing nothing. -
6 Conversion (dry run) (conversion) - The dry run's result: how many objects, how many invalid. -
7 Import (import) Import values Writes the values to the CAFE fields of the same objects, in batches, each object in one transaction. Can be rolled back in History. CAFE values
8 Verification (verification) Verify Compares ACF and CAFE: every field (key, name, type) and every value on every object. -
9 Frontend code (code) Scan code, I checked the remaining code by hand Scans the theme and chosen plugins for ACF calls and shows each change to make (safe, to check, by hand). CAFE never edits theme or plugin files: make the changes in your code editor, scan again, then confirm. Nothing (read-only scan)
10 Final verification (final) Run the final verification Checks every condition again. -
11 Remove ACF data (cleanup) Remove ACF data Only visible when step 10 passed (less than an hour ago). Confirmation dialog, type REMOVE ACF. Removes ACF data (after a backup)
The ACF to CAFE migration steps
The ACF to CAFE migration steps

Fields

ACF CAFE
Text, Text Area, Number, Range, Email, URL, Password same types
WYSIWYG Rich Text
oEmbed, Image, File, Gallery, Link, Google Map same types
Select, Checkbox, Radio Button, Button Group same types
True / False Toggle
Date Picker, Date Time Picker, Time Picker Date, Date and Time, Time (return format kept when CAFE accepts it)
Color Picker Color
Post Object, Page Link, Relationship, Taxonomy, User same types
Message, Tab same types
Accordion Collapsible Section (the fields after it are placed inside it)
Group Field Set
Repeater Collection
Flexible Content Section Builder, one Section per layout
Clone Clone (same field and group keys)
Icon Picker and other types not converted: reported; their ACF data is always kept

Also carried over: required, instructions, default values, choices, widths (snapped to CAFE's widths), conditional logic (==, !=, >, <, empty, not empty, contains; other operators are reported), location rules (post type, post, page template, page type, post category / taxonomy, taxonomy, options page → Settings Page, user role → User Role, user form → User Screen: ACF's sign-up form has no CAFE screen and is reported), position, label and instruction placement, hidden screen elements ("Hide on screen"; an element CAFE cannot hide is left out and reported: ACF's empty value means nothing is hidden). Settings are checked together by the CAFE field type; one CAFE does not accept is left out and reported, never dropped silently. ACF's 0 for "no minimum / maximum" means no limit in CAFE too.

Field names CAFE does not accept (Hero-Title) become Hero_Title (reported); their values follow automatically.

Values

ACF saves each value next to a reference: price = 450000 and _price = field_abc. The conversion reads the values of the fields it knows, following the reference: when two field groups use the same name, a value goes to the field it belongs to.

ACF stores CAFE gets
"1" / "0" true / false
20260929 (date picker) 2026-09-29 (shown in the field's return format)
attachment ID the same attachment ID
serialized ID lists (gallery, relationship, checkbox) lists, order kept
team = 2, team_0_name, team_1_name (repeater) two Collection items
sections = ["hero","text"], sections_0_heading (flexible content) two sections with _section
address_street, address_city (group) one Field Set value
clone values (with or without name prefix) the Clone's value
options_company_phone (options page) cafe_get_option( 'company_phone' )

Values saved by other code. Themes and plugins often save ACF values with update_user_meta(), update_post_meta() or update_option(): without ACF's _name reference row. ACF still shows them (it looks the field up by name), so the conversion finds and converts them too, for the fields of groups shown on that kind of object only (a user role or user form location for users, a taxonomy for terms, an options page for options, a post location for posts) and only when no other field uses the same name. A post field is never filled from a user's meta of the same name.

Values go through CAFE's public API (FieldService::save_values()), never directly into CAFE's tables. Empty ACF values stay empty; they never clear a CAFE value.

Values CAFE's field refuses. ACF does not check a value when it reads it, so ACF data can hold values its own field would not accept, for example 97.450 in a yes/no radio. CAFE does not store such a value. It is left out and reported, and the rest of that object is still converted:

  • steps 6 and 7 show Needs attention and list each left-out value: the object (linked to its edit screen), the field, why CAFE refused it and the value;
  • step 8 counts them as left out, not as mismatches;
  • step 11 keeps them in ACF (the only copy), so they can still be fixed: the rest of that object's ACF data is removed as usual.

To bring such a value over, fix it (in ACF, on the object's edit screen) and run steps 5-8 again. Deleting CAFE data does not change it: the value is in ACF.

Verification

Step 8 compares, for every object with ACF values:

  • the ACF value, converted and sanitized by the CAFE field type (the same step saving runs), with
  • the value CAFE holds for the same field on the same object ID:

row by row for Collections, section by section for Section Builders, sub-field by sub-field for Field Sets and Clones. Media IDs, related IDs and options are part of it. The report shows ACF fields, CAFE fields, objects checked, values migrated, values verified, mismatches and unsupported fields, with the object and field of every mismatch. The verification only counts when it ran after the last import.

A mismatch keeps the removal locked and ACF untouched. Typical causes: someone edited content after the import (import again), or a value CAFE's field type refuses (the dry run lists those first).

Frontend code

Step 9 scans the active theme (and its parent) and the plugins you choose, with PHP's own tokenizer (never regular expressions) so only real function calls are found: not method calls ($object->get_field()), text, comments or function definitions.

Category Example What happens
Safe to change (safe) get_field( 'price' ) on a Number field; update_field, delete_field with a plain post ID; ACF object text with a plain ID (below) Pre-selected; the function name becomes cafe_get_field / cafe_update_field / cafe_delete_field. Each finding shows the line as it will read
Check before changing (review) get_field( 'hero' ) on an Image field (CAFE's image array has no sizes); get_field( 'x', $object ) with an object CAFE may or may not read; get_fields(); a field name that is not fixed text Not selected; you can approve each one after reading the reason
Change by hand (manual) have_rows(), the_row(), get_sub_field(), the_field(), get_field_object(), acf_add_options_page(), acf/… hooks, function_exists( 'get_field' ), get_field( …, …, false ), ACF object text whose ID is not plain ('user_' . ( $a ? $b : $c ), 'user_' . $id . '_x', 'comment_' . $id) Never changed automatically; the reason says what to use instead (for rows: foreach ( cafe_rows( 'team' ) as $row ))

ACF object text is rewritten. ACF passes users and terms as text ('user_' . $id); CAFE takes the ID and the object type. When the ID is plain, the call is rewritten: the ID copied exactly as written, cast to a whole number:

ACF CAFE
get_field( 'loan_tc', 'user_' . $userID ) cafe_get_field( 'loan_tc', (int) $userID, 'user' )
get_field( 'loan_tc', 'user_' . $_POST['user_id'] ) cafe_get_field( 'loan_tc', (int) $_POST['user_id'], 'user' )
get_field( 'x', "user_{$user->ID}" ) cafe_get_field( 'x', (int) $user->ID, 'user' )
get_field( 'x', 'term_' . $term_id ) cafe_get_field( 'x', (int) $term_id, 'term' )
get_field( 'x', 'category_12' ) (any registered taxonomy) cafe_get_field( 'x', 12, 'term' )
update_field( 'x', $v, 'user_' . $id ), delete_field( 'x', 'user_' . $id ) the same with (int) $id, 'user'

Plain IDs: a variable with fixed steps ($userID, $_POST['user_id'], $data['user_id'], $user->ID), a number, get_current_user_id() or get_queried_object_id(). The prefix must be user, term or a registered taxonomy, the object must be the call's last argument, and nothing may follow the ID; anything else stays Change by hand. The rewrite reads PHP's tokens, never regular expressions.

Applying changes:

  1. only files of the scanned theme / plugins, only .php, only if the file did not change since the scan;
  2. a copy of each file is saved in wp-content/uploads/cafe-backups/code-…/ first;
  3. only the function name changes: arguments are never rewritten;
  4. the new code must pass PHP's parser, otherwise the file is left as it was;
  5. the result lists changed and skipped files, and Restore puts a backup back.

When the site forbids file editing (DISALLOW_FILE_EDIT, DISALLOW_FILE_MODS) or the user may not edit themes and plugins, the screen only previews. The step is complete when no ACF usage is left, or when you confirm that the remaining code was checked by hand.

Output differences to check in templates (CAFE's own shapes, not migration errors):

  • an image returned as an array has no sizes key: use wp_get_attachment_image() with the ID;
  • an empty image inside a row is null (ACF: false);
  • a Field Set (ACF group) that was never saved returns an array of empty sub-values, not null: test a sub-value (if ( $address['street'] )), not the array;
  • a Toggle that was never saved returns false (ACF: null);
  • an empty File inside a Field Set is null (ACF: false);
  • text is cleaned the way CAFE saves it: spaces at the end of a Text field are removed, and Windows line breaks (\r\n) in a Text Area become \n. The verification compares after this clean-up, so these count as identical: the text itself does not change.

Final verification and removing ACF data

Step 10 checks, on the server (the list the screen shows, gate.conditions in REST):

key Condition
acf ACF detected
fields CAFE fields created for every supported ACF field
values Values imported without failures
ids Existing object IDs kept (nothing created)
verification Verification passed after the last import
code Frontend code migrated or checked by hand
final Final verification passed (less than an hour ago)

Only when all hold (gate.passed) does Remove ACF data appear. The dialog explains what is removed and what is kept and needs REMOVE ACF typed. The server checks the gate and the text again.

Removed:

  • per object, the ACF rows whose reference points at a migrated field (_price = field_abc) and the value rows they describe (price, team_0_name …);
  • ACF field groups stored in the database whose fields all exist in CAFE.

Kept:

  • all CAFE fields and values;
  • posts, pages, custom post types, media, users, taxonomy terms, WordPress data;
  • ACF values of unsupported fields, and field groups that contain them;
  • field groups registered in PHP or JSON files (remove them from the code);
  • meta of other plugins;
  • values CAFE could not take (see Values CAFE's field refuses above);
  • values that were saved without an ACF reference row (by other code, which may still read them with get_user_meta() and the like): they were converted and verified, but are left in place: the result says how many objects keep such values. They do not keep the field groups from being removed;
  • any object whose values differ from CAFE at the moment of removal: each object is verified again right before its ACF rows are removed.

Before anything is removed, every row is written to a backup file in wp-content/uploads/cafe-backups/ (closed to the web on Apache; the name is unguessable). Restore ACF data from the backup puts every removed row and field group back exactly. CAFE's own rollback does not cover this step: it only undoes CAFE imports.

Deactivate ACF only after the removal and after checking the site.

Options pages → Settings Pages

ACF PRO registers its options pages in code (admin.php?page=site-options). Step 3 creates a CAFE Settings Page with the same slug for each of them: title, menu title, parent menu, icon, position, save button label and who may edit it, so the converted field groups point at a real page and old links keep working.

ACF CAFE Settings Page
menu_slug slug (the same address)
page_title, menu_title title, menu title
parent_slug parent menu: another options page or a WordPress menu CAFE offers; otherwise its own menu (reported)
icon_url (dashicons), position icon, position
update_button save button label
capability the same, or the nearest stricter one CAFE offers (edit_posts → publish_posts); never a looser one (reported)

Until step 11 ACF shows its own page at that address, so the CAFE pages are created switched off. As soon as step 11 removed the ACF data, the CAFE pages take over the address, even while ACF is still active: ACF no longer adds its (now empty) copies of those pages (CAFE removes them from ACF's acf/get_options_pages list; ACF's other options pages are not touched), and the CAFE pages switch on with the converted fields and values. Restoring the ACF data gives the addresses back to ACF and switches the CAFE pages off. Pages that already exist in CAFE are left as they are; a slug CAFE cannot use (upper case, underscores, cafe-…) is reported and not created. Running step 3 again creates nothing new.

Rolling back

  • An import (step 7) is rolled back in History (latest import per kind of object).
  • Code changes: Restore in step 9.
  • Removed ACF data: Restore ACF data from the backup in step 11.
  • Start over forgets the screen's progress only; it never touches ACF or CAFE data.

Also possible

In the migration editor, From ACF opens these steps. The ACF source itself stays available to developers for special cases: field groups from an ACF export file, or values of one post type only: through the REST API and wp cafe migration; the migrations the steps prepare can also be opened in the editor. The same rule applies there: the ACF source only updates the same objects (update only, same object, CAFE fields only) and is refused otherwise.

Tested with

Secure Custom Fields 6.9.5 (the WordPress.org release of ACF) on WordPress 7.1.2: 28/28 fields, 22 values on a post, a page, a custom post type item, a term, a user and an options page (repeater, flexible content, group, clone, image, gallery, relationship, taxonomy, user, date, true/false, select, number), get_field() and cafe_get_field() identical, code scan and apply, removal and restore.