From Shortcode to Dynamic Block: A WordPress Migration

Migrating a WordPress shortcode to a block should not make existing posts stop rendering. The safest approach is incremental: keep the shortcode registered, move output into one shared renderer, introduce a dynamic block that calls that renderer, and convert old content only after the new path is proven. This tutorial migrates [team_member id="42" show_email="no"] to a dynamic block without duplicating business logic or forcing an immediate database rewrite. Editors gain structured controls while visitors continue seeing the same dependable output.
Inventory the shortcode contract
Before writing a block, search real content and record the shortcode’s attributes, defaults, nesting, capitalization, malformed examples, and use inside widgets or custom fields. A migration based only on the documented happy path will fail on the variations editors actually saved.
Define the new contract explicitly: a positive member post ID, a Boolean email flag, and an empty state that reveals no private data. Decide which attributes remain supported and which require a warning or manual review.
Extract one server-side renderer
The shortcode callback and block render callback should share a function that accepts normalized values and returns complete escaped HTML.
function adz_render_team_member( $member_id, $show_email = false ) { $member_id = absint( $member_id ); if ( ! $member_id || 'team_member' !== get_post_type( $member_id ) || 'publish' !== get_post_status( $member_id ) ) { return ''; } $name = get_the_title( $member_id ); $role = get_post_meta( $member_id, 'role', true ); $email = get_post_meta( $member_id, 'public_email', true ); $html = '<article class="adz-team-member">'; $html .= '<h2>' . esc_html( $name ) . '</h2>'; if ( $role ) { $html .= '<p>' . esc_html( $role ) . '</p>'; } if ( $show_email && is_email( $email ) ) { $html .= '<a href="' . esc_url( 'mailto:' . $email ) . '">' . esc_html( $email ) . '</a>'; } return $html . '</article>'; }
The renderer validates the referenced resource and escapes at output. It does not trust an ID merely because it came from saved content.
Keep the legacy shortcode working
Normalize shortcode attributes into the new renderer’s types. This compatibility layer can remain small and stable.
add_shortcode( 'team_member', function ( $atts ) { $atts = shortcode_atts( array( 'id' => 0, 'show_email' => 'no' ), $atts, 'team_member' ); return adz_render_team_member( absint( $atts['id'] ), 'yes' === strtolower( $atts['show_email'] ) ); } );
Do not mark the shortcode obsolete in the user interface until the block can reproduce all supported behavior. Existing content may remain cached, syndicated, or stored outside post content.
Register a dynamic block
Define stable attributes in block.json and render on the server so shortcode and block output remain consistent.
{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "adz/team-member", "title": "Team Member", "category": "widgets", "attributes": { "memberId": { "type": "integer", "default": 0 }, "showEmail": { "type": "boolean", "default": false } }, "supports": { "html": false }, "render": "file:./render.php" }
In render.php, call the shared renderer and add block wrapper attributes to its outer element or pass a safe class option into the renderer. Avoid string replacement on finished HTML because it becomes fragile as markup evolves.
Design the editor around selection, not raw IDs
A block should not ask editors to memorize a post ID. Use an entity selector backed by the REST API, display the selected member’s name, and provide an explicit clear action. Limit the query to the expected post type and the fields required by the control.
Permissions matter in the editor as well. If users can insert the block but cannot list members, show a useful permission message instead of an endless spinner. Never expose private email data in a general REST response merely to populate a label.
Choose a conversion strategy
Three strategies are valid:
- Compatibility only: old shortcodes continue rendering; new content uses the block.
- Editor transform: a shortcode transform converts recognizable instances when an editor opens or chooses to transform them.
- Bulk migration: a WP-CLI command parses posts, replaces unambiguous shortcodes, and records changed IDs.
Start with compatibility. Add a transform for clean cases. Use a bulk migration only when there is a real operational benefit, such as removing a dependency or enabling structured editing across the archive.
Parse instead of using a global regular expression
Shortcodes can contain quoted attributes, escaped brackets, and nested content. Use WordPress shortcode parsing functions to identify candidates, and use the block serializer to generate block markup. Preserve a backup and skip ambiguous content for manual review. A migration that changes 95 percent safely and reports 5 percent is better than one that corrupts the last edge cases.
Verify both paths before cleanup
- Compare shortcode and block output for every supported attribute combination.
- Test missing, private, trashed, and deleted member records.
- Check editor permissions and REST exposure with non-administrator roles.
- Test pages containing several instances and cached output.
- Run any converter twice and confirm the second run changes nothing.
- Keep the shortcode through an observation period and monitor remaining usage.
Only remove the shortcode after an inventory proves that no supported content source uses it. Search post content, widgets, templates, options, and relevant custom fields.
Make the migration reversible
A shared renderer gives both formats the same secure output while the site transitions at its own pace. Preserving the shortcode avoids a flag day; a dynamic block makes structured editing possible; and a resumable converter handles only proven cases. The Shortcode API and dynamic block guide document the two interfaces. The quality of the migration comes from supporting both until real content confirms the old one can retire.
Written by
Adrian Saycon
A developer with a passion for emerging technologies, Adrian Saycon focuses on transforming the latest tech trends into great, functional products.


