
How to Create a Custom Sidebar in WordPress?
Most WordPress themes ship with exactly one sidebar, shown identically on every post, page, and archive. That works fine until it doesn't: your blog sidebar needs a "Recent Posts" widget that makes no sense on your shop pages, or a specific landing page needs a promotional widget that shouldn't appear anywhere else. The default single-sidebar setup simply can't do that, because there's only one widget area to work with.
The fix is to register additional sidebars of your own, then decide, in code or through an editor-facing dropdown, which one shows up where. In this guide, you'll register a custom sidebar, display it safely with a fallback, assign different sidebars to different page types, and, if you want to go further, let editors choose a sidebar per page without touching code at all.
How WordPress Sidebars Actually Work
A "sidebar" in WordPress isn't necessarily a sidebar visually; it's really just a widget area, a named region a theme registers so widgets can be dropped into it from Appearance → Widgets (or the block-based widgets editor). A theme can register as many of these as it wants, and nothing stops you from calling one "Footer Widgets" or "Shop Sidebar" instead of literally putting it beside your content.
Three functions do all the work:
register_sidebar()declares a new widget area and gives it a name, ID, and the HTML wrapper widgets will be rendered inside.dynamic_sidebar()outputs whatever widgets have been added to a given sidebar, wherever you call it in your theme.is_active_sidebar()checks whether a sidebar actually has widgets in it, so you're not rendering an empty wrapper<div>when nothing's been added.
Step 1: Register a Custom Sidebar
Add this to your theme's functions.php:
add_action( 'widgets_init', function () {
register_sidebar( [
'name' => 'Blog Sidebar',
'id' => 'blog-sidebar',
'description' => 'Displayed on single blog posts and the blog archive.',
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
] );
register_sidebar( [
'name' => 'Shop Sidebar',
'id' => 'shop-sidebar',
'description' => 'Displayed on WooCommerce shop and product pages.',
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
] );
} );
A few details worth understanding here:
idis the unique key you'll use to reference this sidebar everywhere else, so keep it lowercase with hyphens and never change it later, since existing widget assignments are stored against this ID.before_widget/after_widgetwrap every individual widget. The%1$sand%2$splaceholders are filled in automatically with a unique widget ID and CSS classes, which you'll want for styling individual widget types differently.- Registering multiple sidebars in the same
widgets_initcallback, as shown above, is perfectly fine; there's no need for a separate hook per sidebar.
Once this code is saved, both sidebars immediately appear as separate widget areas in Appearance → Widgets, ready for you to drag content into.
Step 2: Display the Sidebar in Your Theme
Add this wherever the sidebar should render, for example in a custom sidebar-blog.php template part:
<?php if ( is_active_sidebar( 'blog-sidebar' ) ) : ?>
<aside class="site-sidebar" aria-label="Blog sidebar">
<?php dynamic_sidebar( 'blog-sidebar' ); ?>
</aside>
<?php endif; ?>
Wrapping dynamic_sidebar() in is_active_sidebar() matters more than it looks: without that check, an editor who hasn't added any widgets yet would still see an empty sidebar column eating up layout space. With it, the whole <aside> simply doesn't render until there's actually something to show.
Step 3: Load Different Sidebars for Different Content
Now that you have more than one sidebar registered, you need logic somewhere to decide which one applies where. The cleanest place for this is usually a small helper function, called from a single shared get_sidebar() template:
function tidewave_get_sidebar_id() {
if ( function_exists( 'is_shop' ) && ( is_shop() || is_product() || is_product_category() ) ) {
return 'shop-sidebar';
}
if ( is_singular( 'post' ) || is_home() || is_category() || is_tag() ) {
return 'blog-sidebar';
}
return 'blog-sidebar'; // sensible default
}
Then in sidebar.php:
<?php
$sidebar_id = tidewave_get_sidebar_id();
if ( is_active_sidebar( $sidebar_id ) ) :
?>
<aside class="site-sidebar" aria-label="Sidebar">
<?php dynamic_sidebar( $sidebar_id ); ?>
</aside>
<?php endif; ?>
Because every page now resolves to a sidebar ID through one function, adding a third or fourth sidebar later (say, a "Landing Page Sidebar") only means adding one more condition to tidewave_get_sidebar_id(), not hunting through multiple template files.
Step 4: Let Editors Choose a Sidebar, Without Editing Code
Conditional logic in functions.php works well for content types, but sometimes you want a specific page or post to use a different sidebar, decided by whoever is editing it, not hardcoded by URL pattern. A simple meta box handles this cleanly on the classic editor:
add_action( 'add_meta_boxes', function () {
add_meta_box(
'tidewave_sidebar_select',
'Sidebar',
'tidewave_render_sidebar_meta_box',
[ 'post', 'page' ],
'side'
);
} );
function tidewave_render_sidebar_meta_box( $post ) {
wp_nonce_field( 'tidewave_save_sidebar', 'tidewave_sidebar_nonce' );
$current = get_post_meta( $post->ID, '_tidewave_sidebar', true );
$options = [
'' => 'Default',
'blog-sidebar' => 'Blog Sidebar',
'shop-sidebar' => 'Shop Sidebar',
];
?>
<select name="tidewave_sidebar" style="width: 100%;">
<?php foreach ( $options as $value => $label ) : ?>
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $current, $value ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
<?php
}
add_action( 'save_post', function ( $post_id ) {
if ( ! isset( $_POST['tidewave_sidebar_nonce'] ) ||
! wp_verify_nonce( $_POST['tidewave_sidebar_nonce'], 'tidewave_save_sidebar' ) ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['tidewave_sidebar'] ) ) {
update_post_meta( $post_id, '_tidewave_sidebar', sanitize_text_field( $_POST['tidewave_sidebar'] ) );
}
} );
Then update tidewave_get_sidebar_id() to check for this override first, before falling back to your content-type logic:
function tidewave_get_sidebar_id() {
$override = is_singular() ? get_post_meta( get_the_ID(), '_tidewave_sidebar', true ) : '';
if ( $override ) {
return $override;
}
if ( function_exists( 'is_shop' ) && ( is_shop() || is_product() || is_product_category() ) ) {
return 'shop-sidebar';
}
if ( is_singular( 'post' ) || is_home() || is_category() || is_tag() ) {
return 'blog-sidebar';
}
return 'blog-sidebar';
}
With this in place, an editor can open any post or page, pick a sidebar from a dropdown in the sidebar of the editor screen, and override the default without a developer needing to touch a single conditional tag.
Styling the Sidebar
A minimal layout to get the sidebar sitting alongside your content:
.content-wrapper {
display: flex;
gap: 40px;
align-items: flex-start;
}
.main-content {
flex: 1;
min-width: 0;
}
.site-sidebar {
width: 300px;
flex-shrink: 0;
}
.site-sidebar .widget {
margin-bottom: 32px;
padding: 20px;
border-radius: 10px;
background: #f6f6f6;
}
.site-sidebar .widget-title {
margin: 0 0 12px;
font-size: 16px;
font-weight: 600;
}
@media (max-width: 782px) {
.content-wrapper {
flex-direction: column;
}
.site-sidebar {
width: 100%;
}
}
Custom Sidebars in Block Themes
If your theme uses full site editing (a theme.json and block templates rather than sidebar.php), the classic register_sidebar()/dynamic_sidebar() pair still works exactly as described above as long as your theme declares add_theme_support( 'widgets' ), and it will show up in the block-based widgets editor at Appearance → Widgets the same way. What changes is layout: instead of calling dynamic_sidebar() from a PHP template, block themes typically place a Template Part containing a Widget Area block in the Site Editor, positioned in a columns layout next to your content.
If you're building a block theme from scratch and don't need classic widgets at all, it's often simpler to skip register_sidebar() entirely and instead build the "sidebar" as its own reusable template part made of ordinary blocks (a Latest Posts block, a Search block, and so on), assigned conditionally through block template hierarchy (for example, a dedicated single-post.html template versus page.html) rather than PHP conditionals.
Frequently Asked Questions (FAQ) About Custom WordPress Sidebars
Yes, there's no practical limit. Add as many register_sidebar() calls as you need inside the same widgets_init action, each with its own unique id, and extend your helper function's conditional logic to route to each one.
They aren't deleted. WordPress keeps "inactive widgets" in the database even after a sidebar's register_sidebar() call is removed, and you can reassign them to another sidebar later from the Widgets screen's "Inactive Widgets" section.
This almost always means the register_sidebar() call isn't running, usually because it wasn't hooked to widgets_init, or there's a PHP error earlier in functions.php preventing the rest of the file from executing. Check your site's error log after saving the file.
No, each widget instance belongs to exactly one sidebar. If you want the same content (like a newsletter signup) in multiple sidebars, add it as a separate widget instance to each one, or build it as a reusable template part if you're on a block theme.
A plain meta box, as shown above, is the leaner choice if this is the only custom field your site needs, since it adds zero dependencies. If your site already uses Advanced Custom Fields for other per-post settings, adding a "Sidebar" select field there instead keeps all your custom fields managed in one consistent place.
No database or object cache flush is needed, since sidebars are registered fresh on every page load via the widgets_init hook. If you don't see the change, clear any full-page caching plugin or CDN cache, and confirm the code was actually saved to the theme currently active on the site.
Conclusion
A single sidebar is a fine default, but the moment your site has more than one type of content, it starts working against you instead of for you. Registering a couple of purpose-built sidebars with register_sidebar(), deciding which one applies where with a small helper function, and optionally letting editors override that choice through a meta box gives you the flexibility of a page-builder-style layout system without needing one at all.
Start with the content-type-based routing in Step 3, since it covers the vast majority of real use cases with almost no ongoing maintenance, and only add the per-post override from Step 4 once you actually run into a page that needs to break the pattern.
Here are a few additional resources if you want to go deeper:
- WordPress Developer Reference: register_sidebar() — the full list of arguments the function accepts.
- WordPress Developer Reference: dynamic_sidebar() — how widget output is generated and returned.
- WordPress Developer Reference: add_meta_box() — the full reference for building custom editor meta boxes like the sidebar switcher above.


