wp_parse_args() is WordPress’s utility for merging a set of user-provided arguments with a set of defaults, similar to JavaScript’s Object.assign({}, defaults, userArgs) or PHP’s array_merge($defaults, $user_args). The key difference from a plain array_merge() call is that wp_parse_args() accepts the first argument as either an array or a URL query string (or an object), converting it to an array before merging — making it flexible for functions that want to accept arguments in multiple formats. WordPress itself uses wp_parse_args() extensively in WP_Query, wp_list_comments(), register_post_type(), and virtually every function that accepts an options array. For deep merging of nested arrays, a recursive variant is needed — wp_parse_args() only merges the top level, which is a common source of bugs when defaults contain nested arrays.
Problem: A custom widget function accepts an $args parameter that can be an array, a query string, or an object. The function needs sensible defaults for all unspecified keys, must not error on missing keys, and needs a recursive variant for one nested sub-array ('query_args') that itself has defaults.
Solution: Use wp_parse_args() for the top-level merge. For the nested query_args sub-array, write a recursive helper or use wp_parse_args() a second time on the nested value.
<?php
// ── Basic usage ───────────────────────────────────────────────────────
function render_post_widget( $args = [] ): void {
$defaults = [
'title' => __( 'Latest Posts', 'textdomain' ),
'count' => 5,
'show_date' => true,
'show_thumb' => false,
'query_args' => [
'post_type' => 'post',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC',
],
];
// wp_parse_args accepts: array, URL query string, or stdClass object
$args = wp_parse_args( $args, $defaults );
// ── Nested array requires a second wp_parse_args call ─────────────
// wp_parse_args only merges TOP level — nested arrays are replaced
// if partially specified. Fix by merging the nested array separately:
$query_defaults = $defaults['query_args'];
$args['query_args'] = wp_parse_args( $args['query_args'], $query_defaults );
// Now safe to access all keys:
$query = new WP_Query( $args['query_args'] );
echo '<div class="post-widget">';
if ( $args['title'] ) {
echo '<h3>' . esc_html( $args['title'] ) . '</h3>';
}
while ( $query->have_posts() ) {
$query->the_post();
echo '<p><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></p>';
}
wp_reset_postdata();
echo '</div>';
}
// ── Calling with different argument types ──────────────────────────────
render_post_widget( [ 'count' => 3, 'show_thumb' => true ] );
render_post_widget( 'count=3&show_date=false' ); // URL query string OK
$obj = new stdClass(); $obj->count = 3;
render_post_widget( $obj ); // object OK
// ── Recursive deep merge helper ───────────────────────────────────────
// Use when defaults and user args both have nested arrays at multiple levels
function wp_parse_args_recursive( array $args, array $defaults ): array {
$result = $defaults;
foreach ( $args as $key => $value ) {
if ( is_array( $value ) && isset( $result[ $key ] ) && is_array( $result[ $key ] ) ) {
$result[ $key ] = wp_parse_args_recursive( $value, $result[ $key ] );
} else {
$result[ $key ] = $value;
}
}
return $result;
}
// ── wp_parse_args vs array_merge: key difference ─────────────────────
$defaults = [ 'a' => 1, 'b' => 2 ];
$args = [ 'b' => 99 ];
// array_merge: numeric keys from $args overwrite those from $defaults
// (same for string keys, no special handling of objects/query strings)
array_merge( $defaults, $args ); // ['a' => 1, 'b' => 99] ✓
// wp_parse_args: same result for arrays, but also handles query strings + objects
wp_parse_args( $args, $defaults ); // ['a' => 1, 'b' => 99] ✓
wp_parse_args( 'b=99', $defaults ); // ['a' => 1, 'b' => '99'] ✓ (string values)
NOTE: wp_parse_args() is a shallow merge — if a default has a nested array and the caller passes a partial nested array, wp_parse_args() replaces the entire nested array rather than merging it. This is a frequent source of bugs when writing functions with nested default arrays (like query_args). The recursive helper wp_parse_args_recursive() shown above solves this. When accepting query string arguments ('count=3&show_date=0'), values are always strings after parsing — 0 becomes the string '0' rather than integer 0 or boolean false. Always cast values to the expected type after merging: (int) $args['count'], (bool) $args['show_date']. This is why most WordPress core code uses arrays rather than query strings for internal function arguments, even though wp_parse_args() supports both.