[staff_bio id=""] as an empty div, when a legacy theme shortcode swallows a show_title flag, or when an editorial workflow depends on attributes that were never written down. This article covers the exact methods: reading the registration source, interrogating the database, tracing the render path, and rebuilding the attribute contract from production content.
For small-to-mid publishing teams, the failure is usually not a missing shortcode. It is a shortcode that exists, renders, and quietly drops attributes because the handler expects post_id while the content contains id. The fix is not another plugin. The fix is a precise reconstruction of the expected attribute map, followed by a content correction or a compatibility shim. This article assumes you have database access, a staging environment, and enough skepticism to distrust every comment in the theme’s functions.php.

Why Production Content Is the Only Reliable Contract
Documentation lies. Code comments lie. The only source of truth for a shortcode’s expected attributes is the combination of the registered handler and the content that has survived in production. A shortcode like [author_card] may be documented as accepting name, role, and photo, but the production database may contain [author_card author="Jane" title="Editor" image="jane.jpg"]. The handler may map author to name through a compatibility branch, or it may ignore author entirely and render a blank card. You cannot know which without reading the handler and sampling the content.
Production content is also where attribute drift becomes visible. A shortcode introduced in 2019 may have used columns. A 2021 redesign may have changed the handler to expect cols. The old posts still contain columns. The new posts contain cols. The handler may support both, or it may have a shortcode_atts call that silently discards the old key. Reverse-engineering the expected attributes means finding both the current contract and the historical aliases that production content still relies on.
Step 1: Locate the Shortcode Registration
Start with the registration call. In a theme, it is usually in functions.php or an included file. In a plugin, it is in the main plugin file or a module. Search the codebase for add_shortcode. The second argument is the callback function name. That callback is where the attribute contract lives.
add_shortcode( 'staff_bio', 'render_staff_bio' );
Open the callback. The first thing to look for is the shortcode_atts call. This is the WordPress function that merges user-supplied attributes with defaults. The defaults array is the authoritative list of expected attributes—at least for the current version of the handler.
function render_staff_bio( $atts ) {
$atts = shortcode_atts(
array(
'id' => 0,
'show_title' => 'true',
'layout' => 'compact',
),
$atts,
'staff_bio'
);
// ...
}
That array tells you the handler expects id, show_title, and layout. Anything else in the shortcode tag is discarded. If production content contains [staff_bio post_id="42"], the post_id attribute never reaches the render logic. The handler sees id as 0 and either renders nothing or falls back to a global post object.
But the defaults array is not the whole story. The callback may contain conditional logic that reads additional attributes directly from the $atts array before or after the shortcode_atts merge. It may also call shortcode_parse_atts manually, or it may use a custom parser for nested shortcodes. Read the entire callback, not just the first ten lines.
Step 2: Extract the Actual Attribute Usage From the Database
The registration source tells you what the handler expects. The database tells you what the content actually passes. These two sets rarely match perfectly. Run a query against wp_posts to find every post containing the shortcode tag.
SELECT ID, post_title, post_status
FROM wp_posts
WHERE post_content LIKE '%[staff_bio%'
AND post_status = 'publish';
That gives you the posts. Now you need the raw shortcode instances. A simple approach is to export the matching post content and grep for the shortcode pattern. A more precise approach is to use a script that parses the content with get_shortcode_regex() and extracts the attribute strings.
$pattern = get_shortcode_regex( array( 'staff_bio' ) );
foreach ( $posts as $post ) {
preg_match_all( '/' . $pattern . '/s', $post->post_content, $matches );
foreach ( $matches[3] as $atts_string ) {
$atts = shortcode_parse_atts( $atts_string );
// Log $atts for analysis.
}
}
This gives you the exact attribute keys and values used in production. Compare that list to the defaults array from the handler. The differences are your drift. Common findings:
- Production uses
post_id; handler expectsid. - Production uses
show_title="false"; handler expectsshow_title="0"orshow_title="no". - Production uses
align="left"; handler expectsalign="alignleft". - Production passes an attribute that no longer exists in the handler, such as
author_bioafter a redesign.
Each mismatch is a potential silent failure. The shortcode renders, but the output is wrong. The editor sees a broken layout and blames the theme. The actual cause is an attribute contract that drifted without a migration script.
Step 3: Trace the Render Path for Conditional Attributes
Some shortcodes do not use shortcode_atts at all. They read attributes directly from the $atts array and apply their own defaults inline. This is common in older themes and in shortcodes written by developers who did not trust the WordPress API.
function render_legacy_pullquote( $atts ) {
$align = isset( $atts['align'] ) ? $atts['align'] : 'left';
$cite = isset( $atts['cite'] ) ? $atts['cite'] : '';
// ...
}
In this case, the expected attributes are whatever the callback explicitly checks with isset or array_key_exists. The only way to reconstruct the contract is to read every conditional branch. Look for attributes that are read only when another attribute has a specific value. A shortcode may accept source="manual" and then read author_name and author_url only in that branch. Production content may contain those attributes, but they are ignored when source is auto.
Also check for attributes that are passed to a nested function or a template part. The shortcode callback may extract layout and then pass the entire $atts array to a template file. That template file may read additional keys that are not in the defaults array. The contract is then split across two files. You need to trace the full render path, not just the callback.

Step 4: Reconstruct the Attribute Map and Document the Aliases
Once you have the handler defaults, the conditional reads, and the production usage, build a single attribute map. For each attribute, record:
- The canonical key the handler expects.
- The default value applied when the attribute is missing.
- The type or format the handler expects (string, integer, boolean string, comma-separated list).
- Any legacy aliases that production content still uses.
- Whether the attribute is required for meaningful output.
For the staff_bio example, the map might look like this:
| Canonical key | Default | Type | Legacy aliases | Required |
|---|---|---|---|---|
id |
0 |
integer | post_id, user_id |
Yes |
show_title |
true |
boolean string | title |
No |
layout |
compact |
string | style |
No |
This map is the deliverable. It tells you exactly what to fix in production content and what to add to the handler for backward compatibility. Without it, you are guessing.
Step 5: Fix the Drift Without Breaking the Editorial Workflow
There are two ways to close the gap between production content and the handler contract. The first is to update the content. The second is to update the handler. The right choice depends on the volume of affected posts and the risk of touching live content.
If the drift is limited to a few dozen posts, a targeted content update is safer. Use a script that reads each post, parses the shortcode attributes, and rewrites the shortcode tag with the canonical keys. Run it in a staging environment first. Verify the rendered output before pushing to production.
If the drift affects hundreds of posts, or if the content is edited by multiple people who will continue to use the old keys, add an alias layer to the handler. Before the shortcode_atts call, map legacy keys to canonical keys.
function render_staff_bio( $atts ) {
$atts = shortcode_atts(
array(
'id' => 0,
'show_title' => 'true',
'layout' => 'compact',
),
$atts,
'staff_bio'
);
// Legacy alias: post_id -> id
if ( isset( $atts['post_id'] ) && ! isset( $atts['id'] ) ) {
$atts['id'] = $atts['post_id'];
}
// Legacy alias: title -> show_title
if ( isset( $atts['title'] ) && ! isset( $atts['show_title'] ) ) {
$atts['show_title'] = $atts['title'];
}
// ...
}
This keeps old content rendering correctly while new content uses the canonical keys. It also gives you time to update the editorial guidelines and retrain the team. The alias layer is technical debt, but it is visible, documented debt—not a silent failure.
Common Failure Modes When Reverse-Engineering Shortcode Attributes
Boolean attributes that are not boolean
WordPress shortcode attributes are strings. A handler that expects show_title="true" may break when content contains show_title="1" or show_title="yes". The shortcode_atts function does not coerce types. If the handler checks if ( 'true' === $atts['show_title'] ), then show_title="1" evaluates to false. Production content may contain every variant. Reconstruct the expected values, not just the keys.
Attributes that are read before the defaults merge
Some callbacks read $atts before calling shortcode_atts. This is a bug, but it exists in production themes. If the callback checks if ( isset( $atts['id'] ) ) before the merge, then a missing id attribute behaves differently than a default id of 0. The contract is not just the defaults array; it is the order of operations in the callback.
Nested shortcodes that pass attributes through
A shortcode may contain another shortcode, and the outer shortcode may pass attributes to the inner one. For example, [section layout="grid"][card title="One"][/section]. The section handler may extract layout and then pass the remaining attributes to the card handler. The expected attributes for card are then partially defined by the section handler. Reverse-engineering requires tracing the nested render path, not just the individual shortcode registrations.
Attributes that are used only in a specific context
A shortcode may behave differently in a widget, a block template, or a REST API response. The handler may read context from a global variable and apply different defaults. Production content may contain attributes that are only relevant in one context. The attribute map must account for context-dependent behavior, or you will “fix” content that was actually correct.
Tools for the Job
You do not need a commercial plugin for this work. The tools are already in WordPress core and your database client.
get_shortcode_regex()— returns the regex pattern for matching shortcodes in content.shortcode_parse_atts()— parses an attribute string into an associative array.shortcode_atts()— merges user attributes with defaults; the source of truth for the current contract.wp db query— WP-CLI command for running SQL against the database without leaving the terminal.wp post list— WP-CLI command for listing posts that match a content search.
For a one-off audit, a small PHP script run via WP-CLI is usually faster than a plugin. The script can load WordPress, query the posts, parse the shortcodes, and output a CSV of every attribute key and value found in production. That CSV becomes the basis for the attribute map.
When to Write a Compatibility Shim Instead of Fixing Content
There is a point where fixing content is the wrong move. If the shortcode is used in thousands of posts, if the content is edited by a large team, or if the legacy attributes are deeply embedded in the editorial workflow, a compatibility shim in the handler is the pragmatic choice. The shim maps legacy keys to canonical keys and logs a deprecation notice for future cleanup.
The shim should be explicit. Do not use a generic loop that maps every unknown key to a canonical key. That hides drift instead of fixing it. Instead, list each legacy alias with a comment explaining when it was introduced and when it can be removed.
/**
* Legacy alias map for staff_bio shortcode.
*
* post_id -> id (introduced 2019, still used in 214 posts)
* title -> show_title (introduced 2020, still used in 87 posts)
* style -> layout (introduced 2021, still used in 12 posts)
*/
$legacy_aliases = array(
'post_id' => 'id',
'title' => 'show_title',
'style' => 'layout',
);
foreach ( $legacy_aliases as $legacy_key => $canonical_key ) {
if ( isset( $atts[ $legacy_key ] ) && ! isset( $atts[ $canonical_key ] ) ) {
$atts[ $canonical_key ] = $atts[ $legacy_key ];
}
}
This is the kind of fix that keeps a publishing team moving without pretending the drift never happened. It also gives you a clear list of content to update when there is time.
Documenting the Contract for the Editorial Team
The final step is not technical. It is editorial. The attribute map you built needs to live somewhere the team can find it. A private page on the site, a shared document, or a comment block in the theme’s functions.php all work. The key is that the map is specific: canonical keys, accepted values, defaults, and examples.
Do not write “The staff_bio shortcode accepts several attributes.” Write:
[staff_bio id="42" show_title="true" layout="compact"]
id— required. The post ID of the staff member. Do not usepost_id; it is a legacy alias and will be removed.
show_title— optional. Acceptstrueorfalse. Defaults totrue. Do not use1or0.
layout— optional. Acceptscompactorfull. Defaults tocompact. Do not usestyle.
That is a contract. It tells the editor exactly what to type and what to avoid. It also gives the next developer a clear starting point when the shortcode needs to change again.

FAQ
How do I find every shortcode used in a WordPress site?
Run a database query against wp_posts for the shortcode bracket pattern, or use a script with get_shortcode_regex() to extract all registered shortcode tags from post content. The wp post list WP-CLI command with a --s search flag can also locate posts containing a specific shortcode string. For a full inventory, query the post_content column and parse each post with the regex pattern.
What is the difference between shortcode attributes and shortcode parameters?
In WordPress, the terms are often used interchangeably, but the technical distinction is that attributes are the key-value pairs inside the shortcode tag—[shortcode key="value"]—while parameters are the values passed to the handler function after parsing. The shortcode_atts function merges the parsed attributes with defaults, and the resulting array is the parameter list the callback receives. When reverse-engineering, focus on the attribute keys in the content and the parameter names in the callback.
Why does my shortcode render but ignore some attributes?
The most common cause is a key mismatch between the content and the handler’s defaults array. If the content uses post_id and the handler expects id, the shortcode_atts merge discards the unknown key. Another cause is a boolean value mismatch: the handler checks for true but the content passes 1. Read the handler’s shortcode_atts call and compare the keys and expected values to the actual attributes in the database.
Can I reverse-engineer a shortcode without access to the theme or plugin files?
You can reconstruct the attribute usage from the database alone, but you cannot know the handler’s expected defaults or conditional logic without reading the source. The database shows what content passes; the source shows what the handler accepts. For a complete contract, you need both. If the source is unavailable, you can infer likely defaults by testing the shortcode with different attribute combinations on a staging site and observing the rendered output.
Next Step: Build a Shortcode Attribute Registry
This article is the first step in a larger project: a shortcode attribute registry for your publishing stack. The registry is a single document or database table that lists every shortcode, its canonical attributes, accepted values, defaults, and legacy aliases. It becomes the reference for editors, developers, and anyone debugging a rendering issue. The next article in this series will cover how to build that registry from the attribute maps you create here, including a WP-CLI script that audits production content against the registry and reports drift automatically.
If you are dealing with a site that renders nothing at all, the problem may not be a shortcode. It may be a permalink or query issue. See What to Fix First When a New WordPress Site Says Nothing Found for the diagnostic order that catches the most common silent failures before you touch a single shortcode.















