Appearance
WordPress Compatibility
Introduction
A site moves from WordPress to kitloom by pointing kitloom at its database. Nothing is converted: kitloom reads the tables, options and meta as WordPress and its plugins left them, and the site keeps its content, its addresses and its settings.
The move is one way. kitloom writes new arrays as JSON (see Data), so WordPress is not expected to read the database back. How to rehearse it on a copy and compare the two sites page by page: Migrate from WordPress.
What is picked up
WordPress itself
| What | Where WordPress keeps it | kitloom |
|---|---|---|
| Posts, pages, attachments, revisions | posts, postmeta | kitloom/wp-schema |
| Categories, tags, custom taxonomies | terms, term_taxonomy, term_relationships, termmeta | kitloom/wp-schema |
| Comments | comments, commentmeta | kitloom/wp-schema |
| Users, their passwords | users (WordPress's password hashes), usermeta | kitloom/wp-schema (the wordpress user provider and hash driver) |
| Roles | usermeta ({prefix}capabilities, {prefix}user_level), the {prefix}user_roles option | kitloom/permissions — who holds which role is shared with WordPress |
| Sign-up | users_can_register, default_role | kitloom/wp-schema, kitloom/permissions |
| Addresses | permalink_structure, category_base, tag_base, show_on_front, page_on_front, page_for_posts | kitloom/site |
| Site settings | blogname, blogdescription, date_format, posts_per_page, sticky_posts… | kitloom/settings, kitloom/site |
| Page templates | _wp_page_template meta | kitloom/site |
| Blocks | block comments in post_content; synced patterns as wp_block posts | kitloom/site; dynamic blocks (latest posts, latest comments, archives, calendar, categories, search, tag cloud, page list) by kitloom/widgets |
| Menus | nav_menu terms, nav_menu_item posts with _menu_item_* meta, locations in theme_mods_{stylesheet} | kitloom/navigation |
| Widgets | sidebars_widgets, widget_{type} options | kitloom/wp-schema (zones and layout), kitloom/widgets (WordPress's standard widgets) |
| Media | attachment posts, _wp_attached_file, _wp_attachment_metadata, _thumbnail_id; files under /wp-content/uploads/Y/m/ | kitloom/media |
| Media shortcodes | [gallery], [caption], [audio], [video], [playlist], [embed] in content | kitloom/media |
Plugins
| Plugin | What is kept | kitloom |
|---|---|---|
| Rank Math SEO | rank-math-options-titles, -general, -sitemap, -instant-indexing options; rank_math_title, rank_math_description, rank_math_robots, rank_math_canonical_url, rank_math_primary_category… meta; template variables (%title% %sep% %sitename%) | kitloom/seo, kitloom/site |
| Rank Math Redirections, 404 Monitor | rank_math_redirections, rank_math_redirections_cache, rank_math_404_logs tables; their keys in rank-math-options-general | kitloom/redirects |
| Yoast SEO (imported once) | _yoast_wpseo_* post meta, the wpseo_taxonomy_meta option, wpseo_* user meta; wpseo, wpseo_titles, wpseo_social options — read once and written as Rank Math's keys above | kitloom/seo |
| Redirection (imported once) | redirection_items, redirection_groups tables and redirection_options — read once and written as Rank Math's redirections | kitloom/redirects |
| Yoast SEO Premium's redirects (imported once) | the wpseo-premium-redirects-base option (before Premium 3.1 wpseo-premium-redirects and wpseo-premium-redirects-regex) — read once and written as Rank Math's redirections | kitloom/redirects |
| Rank Math Link Counter | rank_math_internal_links, rank_math_internal_meta tables | kitloom/internal-links |
| ACF, Secure Custom Fields | field groups and fields as acf-field-group / acf-field posts; values under the field's meta key with the _{name} reference; options as options_{name} | kitloom/custom-fields |
| ACF post types and taxonomies | acf-post-type, acf-taxonomy posts | kitloom/post-types |
| Contact Form 7 | wpcf7_contact_form posts and their _form, _mail, _mail_2, _messages, _additional_settings meta; [contact-form-7] shortcodes | kitloom/forms |
| Flamingo | messages as flamingo_inbound posts, the form they came from as a flamingo_inbound_channel term | kitloom/forms |
| WooCommerce | product posts, product_cat, product_tag, product_type, product_visibility; _price, _sku, _stock… meta; orders in the HPOS tables (wc_orders, wc_orders_meta, wc_order_addresses, woocommerce_order_items…); woocommerce_* options | kitloom/shop |
| Slide Anything | sa_slider posts with sa_* meta; [slide-anything] shortcodes | kitloom/slider |
| Nextend Social Login | linked Google and GitHub accounts in its social_users table; [nextend_social_login] shortcodes | kitloom/social-accounts |
Some modules take a plugin's settings or records once, on request: the module's settings page shows an Import button when the plugin's data is there, and an artisan command does the same. The plugin's own options and tables are left as they are, and importing again adds nothing.
| Plugins | Import | kitloom |
|---|---|---|
| Site Kit by Google, MonsterInsights, Plausible Analytics, Connect Matomo, Yandex Metrica | php artisan analytics:import | kitloom/analytics |
| GTM4WP, Site Kit by Google (Tag Manager) | php artisan tag-manager:import | kitloom/tag-manager |
| Meta pixel for WordPress, TikTok, LinkedIn Insight Tag, PixelYourSite | php artisan ad-pixels:import | kitloom/ad-pixels |
| Complianz, CookieYes | php artisan cookie-consent:import | kitloom/cookie-consent (Complianz's [cmplz-manage-consent] keeps working) |
| Yoast SEO — its settings and the head meta of posts, terms and authors | php artisan seo:import --from=yoast | kitloom/seo, into Rank Math's keys; what is set here already stays |
| Redirection — its rules with their hits | php artisan redirects:import | kitloom/redirects; what cannot come over is listed with the reason |
| Yoast SEO Premium — its redirects | php artisan redirects:import --from=yoast | kitloom/redirects; a source with a rule of its own here keeps it |
Other modules do the job of a plugin without taking its data: kitloom/header-footer (Insert Headers and Footers, WPCode — snippets are pasted again), kitloom/two-factor (Two Factor, Wordfence — users set up their second factor again).
Content can also come in as a file: kitloom/import-export reads and writes WXR, the format of WordPress's Tools → Import / Export.
A module for a plugin's data
When a module replaces a WordPress plugin, the plugin's data is the module's data model. The site moves over and the module works with what is there.
1. Find out how the plugin stores things
Install the plugin on a WordPress site, use every feature, and look at what it wrote: its tables (SHOW CREATE TABLE), its options (SELECT * FROM wp_options WHERE option_name LIKE 'plugin%'), the meta keys of the records it touched. Note the formats — serialized arrays, on/off or 1/0 flags, IDs as strings.
Save samples as fixtures in the module's tests/Fixtures/ — rows exactly as the plugin wrote them. kitloom/custom-fields keeps a snapshot of every field type as Secure Custom Fields stores it.
2. Keep its tables and keys
php
// The plugin's table, created only when the site never had the plugin.
if (! Schema::hasTable('rank_math_redirections')) {
Schema::create('rank_math_redirections', function (Blueprint $table): void {
$table->id();
$table->text('sources');
$table->text('url_to');
$table->unsignedSmallInteger('header_code');
// …the rest as the plugin creates them
});
}Use the plugin's option names and meta keys as they are. Name them once, in a class of the module — ContactForm7 in kitloom/forms, RankMath\Options in kitloom/seo — so the rest of the code speaks in plain names.
3. Read both formats, write JSON
php
use Kitloom\WpSchema\Support\StoredArray;
$sources = StoredArray::toArray($row->sources); // serialized by the plugin or JSON by kitloom
$row->sources = StoredArray::encode($sources); // JSON from now onOptions that are one array with many keys — Rank Math keeps whole screens in one option — are read and written key by key: change the keys your module owns and leave the others as they are.
4. Test against what the plugin wrote
php
#[Test]
public function sources_are_read_from_what_rank_math_saved(): void
{
$saved = 'a:1:{i:0;a:3:{s:6:"ignore";s:4:"case";s:7:"pattern";s:8:"Old-Page";s:10:"comparison";s:5:"exact";}}';
$this->assertEquals([new Source('Old-Page', Comparison::Exact, ignoreCase: true)], Sources::decode($saved));
}Run the module on a copy of a real site's database before calling it done: the plugin's data has cases no fixture thought of.
What kitloom does not do
- Run WordPress plugins or themes. PHP code written for WordPress does not run; a plugin's data is supported when a kitloom module for it exists, and a theme is rewritten as a kitloom theme.
- Keep the database readable by WordPress. New arrays are JSON; WordPress would not understand them. Moving back is not supported.
- Carry over what lived outside the database. Visitors' cookie choices and sessions stay behind. Uploaded files are copied by hand — the contents of
wp-content/uploads/into the media disk, keeping theY/mfolders; old links to/wp-content/uploads/…then get a 301 to the new address (kitloom/media). - Run the block editor. Blocks in content are drawn as WordPress draws them; the admins edit the HTML with a visual editor (TinyMCE), not with Gutenberg.
- Copy the site for you. The data moves with the database — or as a WXR file with
kitloom/import-export.