Skip to content
System nominalUTC --:--:--Articles 107

A builder migration is a coverage number, not a switch

Divi 5's migrator converts supported modules and leaves the rest on the old framework. How to count module coverage across pages before scheduling a migration.

Tomas Lindqvist · @tlindqvist

Hardening and operations

Topic
WP
Section
WordPress Internals
Published
19 OCT 2026
Reading time
9 MIN
Words
1994W
Technical cover graphic for A builder migration is a coverage number, not a switch
Generated schematic — WP series, REV 2.6.

The ticket is written in a meeting where nobody has opened the database. A site runs on Divi 4, somebody has read that there is a newer version, and the question on the table is whether the migration is supported. The answer is yes. The ticket says so, the work is scheduled, and nobody asks what "supported" covers and what it leaves out.

Disclosure: WPSifero earns a commission on sales made through the Elegant Themes link in this article. The price you pay is the same. Nothing here is a review: nobody at WPSifero has built a site in Divi, and every statement about the product is taken from Elegant Themes' own published pages, cited where it is used.

A year later the site is described as migrated, and the description is not false. It is also not finished. A page nobody has edited since launch still loads the Divi 4 framework alongside the new one, because a single module on it was never converted, and no one wrote down before the project started what would have counted as complete.

That gap is not specific to Divi. Every builder migration has the same shape: a converter that handles most of the old structures, and a remainder that keeps running on the runtime it was built for. What changes between products is how the remainder is handled and how plainly the vendor says so. Elegant Themes is unusually explicit about this one, which makes it the better case study.

The argument below is therefore about the arithmetic of a migration rather than about whether Divi 5 is good. The same reasoning applies to any builder that ships a converter with an exception list.

The migrator arrives with an exception clause

Elegant Themes' documentation for the Divi 5 Migrator states that the tool converts supported modules to the Divi 5 framework, and that modules not yet supported continue to run wrapped in the Divi 4 framework. It then goes further than most vendors would: a page containing one of those modules loads the Divi 4 framework for that page, and the documentation says this can affect performance. That is their caveat in their words, and it is the sentence that should decide how the project is planned.

Note what the exception attaches to. Not the site, not the page, not the migration run, but the module. Divi 4 stored a page as shortcodes in the post content, tags such as [et_pb_text ...]. Divi 5 uses a block-based framework that does not use shortcodes. The converter is therefore translating one storage format into another instance by instance, and an instance it cannot translate stays where it is with a compatibility layer around it.

That makes conversion a property of each object and coverage a statistic about the whole site, and those are not the same thing. It also means "migrated" names an action rather than a state. The action is a pass over the stored content. The state is a percentage, and nobody wrote it down.

Coverage is counted in module types, not pages

Take a page with fifteen modules, fourteen of which have a Divi 5 equivalent. It is a wrapped page. The exception is small as a count and total as a consequence, and any report that files pages as migrated or not will get this wrong in the direction that flatters the project.

Two numbers are worth separating. Content coverage is converted module instances divided by total module instances, which is the number a converter can report for itself. Page coverage is pages whose modules were all converted, divided by published pages, which is the number that decides what the site actually runs. The second is always the lower of the two, and it is the one to plan against.

The global library is where the arithmetic turns. Elegant Themes' documentation defines a global library item as a Module, Row or Section that appears the same on whatever pages it is added to, with an edit to it changing every page that uses it. One global footer, saved once, can appear on a large share of the site without a single page repeating it. Count module types per page and that footer is a single row in a spreadsheet. Count pages that will keep loading the old framework and it may be most of the site.

The same property becomes an opportunity once the list exists. Replacing one unsupported global item is a single piece of work that clears every page using it. Left alone, it keeps every one of those pages wrapped for as long as it survives. The library decides the shape of the migration, and it is usually audited last, after the page inventory.

Estimating coverage before the migration runs

Someone has to count before any of this can be decided, and the count is possible in advance because of how Divi 4 stored content. Shortcodes in the post content are queryable. Against a standard wp_ prefix, the number of published pages containing at least one Divi 4 shortcode is one query.

SELECT COUNT(*) AS pages_with_divi4
FROM wp_posts
WHERE post_type IN ('page', 'post')
  AND post_status = 'publish'
  AND post_content REGEXP '\\[et_pb_';

The histogram is the useful query, because it turns one number into a list. Naming tags in a union means the query answers for the modules the site is suspected of using, and each count can then be compared with the modules Elegant Themes documents as supported.

SELECT t.tag, COUNT(DISTINCT p.ID) AS pages_using_tag
FROM (
  SELECT 'et_pb_text' AS tag
  UNION ALL SELECT 'et_pb_code'
  UNION ALL SELECT 'et_pb_contact_form'
  UNION ALL SELECT 'et_pb_tabs'
  UNION ALL SELECT 'et_pb_sidebar'
) AS t
JOIN wp_posts p
  ON p.post_content REGEXP CONCAT('\\[', t.tag, '([[:space:]\\]])')
WHERE p.post_type IN ('page', 'post')
  AND p.post_status = 'publish'
GROUP BY t.tag
ORDER BY pages_using_tag DESC;

Two caveats, because this is the step that produces confident wrong numbers. The tag list above is a placeholder, not a description of any site; it has to be completed from the site's own post content, and the names must match what actually appears there. A SQL count also includes rows that may never be served by the builder, so the total is a ceiling rather than a measurement. Both mistakes push the same way, which is why the figure is an estimate before the migration and not a promise.

Getting the full tag inventory, rather than guessing names, does not need SQL string surgery. A dump of the posts table is enough.

mysqldump --no-create-info --skip-extended-insert wp_posts \
  | grep -o 'et_pb_[a-z_]*' \
  | sort | uniq -c | sort -rn

That counts occurrences rather than pages, which is enough to decide which tags deserve the per-page query, and it costs nothing to run against a copy of the database. Run it on production read-only if there is no copy, but run it before the migration, not after.

Three questions then turn the list into a plan, and none of them requires a licence to answer. Which modules are on the supported list today, and where is that list published? For a page carrying one unsupported module, which framework renders the whole page? And when a module is added to the supported list, does an already-migrated page need to be saved again before it stops loading the old framework? The third decides whether coverage can improve without a second pass over the content, so it is worth an email rather than an assumption.

Why the wrapped pages carry the cost

Whatever the answers to those questions, the pages on the exception list have a property the rest do not. A wrapped page is not a page that was half converted and therefore runs at half speed. At request time it loads the old framework, and Elegant Themes states that a page containing a module not yet supported loads the Divi 4 framework, which they say can affect performance. How much, and on which pages, is a measurement nobody at WPSifero has made, and no number is offered here for it.

What can be reasoned about is the shape of the cost. A page in that state has both code paths available to it: the one the site is supposedly running on, and the old one that must be present for the unconverted module. The cost does not attach to the module. It attaches to every page carrying one. That is the difference between a migration that leaves a sliver of its content behind and one that leaves that sliver behind plus every page carrying it.

There is a second cost that has nothing to do with speed. While one page loads the Divi 4 framework, that framework is a live dependency of the site. It is code that has to keep working, and its presence is not visible in the editor, only in what the page loads.

Why a third of the site is still wrapped a year later

The pattern repeats because the exceptions are not random. They cluster on the pages nobody rewrites: legal pages, old campaign landings, a contact page built once and never touched, a template section that came with the child theme. The pages with the most module variety are the ones most likely to hold an unsupported module, and they are also the ones with the least editorial traffic afterwards.

New content makes the headline number improve without touching the tail. A page added after the migration has no legacy shortcodes in its content, so page coverage rises month by month while the absolute number of wrapped pages stays exactly where the migration left it. A dashboard that shows a percentage moving in the right direction will hide a list that has not changed.

The definition of done is the other reason. "Migrated" was settled at ticket close, by whoever ran the pass, and it described the run rather than the site. Nobody redefined it afterwards, so nothing failed a check it was never given. This is not a failure of the tool. It is what happens when a project defines completion by an action instead of a state.

The order of work

The sequence that avoids most of this is short: measure coverage, list the exceptions, decide about each one, and run the migration last. The order is not a preference. Running the migration first converts the easy majority, which makes the wrapped minority harder to see, because the pages that look finished now outnumber the ones that are not. The report becomes true and useless at the same time.

Deciding about an exception means two possible answers rather than one. Replace the module, which is content and design work with a review attached, or keep it wrapped on purpose, which is a legitimate choice for a page that is about to be retired. The second answer needs an owner and a date, because a deliberate exception that nobody owns becomes the permanent tail described above.

One of those decisions has a price attached, and it is worth naming plainly. If the exceptions are few and the site is staying on the product, the licence is the cheapest part of the plan. Elegant Themes' pricing page lists Divi at $89 billed yearly and Divi Lifetime at $249, as published on 2026-10-19, and its own comparison table sets one year at $89 against ten years at $249. Prices change, so check the page rather than this paragraph.

The decision rule

Do not schedule the migration pass until the coverage number exists and every item on the exception list has an answer. If the list is short, if none of it is global, and if the wrapped pages are the ones already queued for deletion, migrate now and delete afterwards. If a global section, header or footer is on the list, replace that item first, because the migration can wait and the library cannot.

Then "wrapped" is a word with a number behind it, and "migrated" stops being a synonym for finished.

Tomas Lindqvist · @tlindqvist

Hardening and operations

Threat models plugins, audits capability and nonce handling, and builds the observability that tells you an integration broke before a customer does.

Continue

Related in WordPress Internals