The default WordPress dashboard is full of widgets your clients do not need. A page you design yourself, with links to the screens they use, short instructions and your contact details, is much more useful.
This snippet replaces the WordPress dashboard with any page of your site. Build the page in the block editor, Bricks, Oxygen, Elementor or another builder, put its URL in the config, and it fills the dashboard area next to the admin menu.
The snippet
<?php
/**
* Replace the WordPress dashboard with a page built in the block editor or a page builder.
*/
// ===== Config =====
function dpc_custom_dashboard_config() {
$config = array(
// The page shown as the dashboard. A page on this site works best.
'url' => home_url( '/dashboard/' ),
// true = administrators keep the normal dashboard, everyone else gets the page.
'skip_admins' => false,
);
// ===== End of config =====
return (array) apply_filters( 'dpc_custom_dashboard_config', $config );
}
/** Whether the current user gets the custom dashboard. */
function dpc_custom_dashboard_applies() {
$config = dpc_custom_dashboard_config();
if ( '' === (string) ( $config['url'] ?? '' ) ) {
return false;
}
return ! ( ! empty( $config['skip_admins'] ) && current_user_can( 'manage_options' ) );
}
// 1. Remove every dashboard widget and the Welcome panel.
add_action( 'wp_dashboard_setup', 'dpc_custom_dashboard_remove_widgets', PHP_INT_MAX );
function dpc_custom_dashboard_remove_widgets() {
global $wp_meta_boxes;
if ( ! dpc_custom_dashboard_applies() ) {
return;
}
foreach ( (array) ( $wp_meta_boxes['dashboard'] ?? array() ) as $context => $priorities ) {
foreach ( (array) $priorities as $widgets ) {
foreach ( array_keys( (array) $widgets ) as $widget_id ) {
remove_meta_box( $widget_id, 'dashboard', $context );
}
}
}
remove_action( 'welcome_panel', 'wp_welcome_panel' );
}
// 2. CSS that places the frame next to the admin menu and below the toolbar.
add_action( 'admin_enqueue_scripts', 'dpc_custom_dashboard_style' );
function dpc_custom_dashboard_style( $hook_suffix ) {
if ( 'index.php' !== $hook_suffix || ! dpc_custom_dashboard_applies() ) {
return;
}
$css = '#dpc-custom-dashboard{position:fixed;top:32px;left:160px;z-index:9989;width:calc(100% - 160px);height:calc(100% - 32px);border:0;background:#fff}'
. '.folded #dpc-custom-dashboard{left:36px;width:calc(100% - 36px)}'
. '@media (max-width:960px){.auto-fold #dpc-custom-dashboard{left:36px;width:calc(100% - 36px)}}'
. '@media (max-width:782px){.auto-fold #dpc-custom-dashboard{top:46px;left:0;width:100%;height:calc(100% - 46px)}}';
wp_register_style( 'dpc-custom-dashboard', false, array(), null );
wp_add_inline_style( 'dpc-custom-dashboard', $css );
wp_enqueue_style( 'dpc-custom-dashboard' );
}
// 3. The frame itself, inside the admin page markup.
add_action( 'admin_footer', 'dpc_custom_dashboard_frame' );
function dpc_custom_dashboard_frame() {
$screen = get_current_screen();
if ( ! $screen || 'dashboard' !== $screen->id || ! dpc_custom_dashboard_applies() ) {
return;
}
$config = dpc_custom_dashboard_config();
printf(
'<iframe id="dpc-custom-dashboard" src="%1$s" title="%2$s"></iframe>',
esc_url( add_query_arg( 'dpc_dashboard', '1', $config['url'] ) ),
esc_attr__( 'Dashboard', 'dpc' )
);
}
// 4. Inside the frame: no second toolbar, and links open in the full window.
add_filter( 'show_admin_bar', 'dpc_custom_dashboard_admin_bar' );
function dpc_custom_dashboard_admin_bar( $show ) {
return dpc_custom_dashboard_is_frame() ? false : $show;
}
add_action( 'wp_head', 'dpc_custom_dashboard_base_target', 1 );
function dpc_custom_dashboard_base_target() {
if ( dpc_custom_dashboard_is_frame() ) {
echo '<base target="_top">' . "\n";
}
}
function dpc_custom_dashboard_is_frame() {
// Only switches the toolbar off and changes the link target, so no nonce is needed.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return isset( $_GET['dpc_dashboard'] ) && '1' === $_GET['dpc_dashboard'];
}
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, set the url in the config and publish. Leave Action/Hook empty.
Everywhere is needed for step 4: the page inside the frame is a front-end request, and that is where the admin bar is hidden. The dashboard parts only run on dashboard hooks, so the front end otherwise does nothing. The CSS is printed from PHP, so no separate header code is needed.
The same code also works unchanged in a small plugin or an mu-plugin.
How it works
wp_dashboard_setupruns after every plugin has added its widgets. At the latest priority the snippet removes all of them, in every column and priority, and switches off the Welcome panel. Theforeachloops cast to arrays, so empty columns no longer cause PHP 8 warnings.- The frame is printed on
admin_footer, inside the admin page. The earlier version echoed it onwp_dashboard_setup, which runs before WordPress prints<html>, so the markup landed at the very top of the document. - The CSS places the frame below the toolbar and next to the admin menu, and follows the folded menu and the mobile layout. It is only added on the dashboard screen.
- The frame URL gets
?dpc_dashboard=1. With that parameter the front end hides its admin bar and adds<base target="_top">, so links in your page open in the whole window instead of inside the frame. - The URL is escaped with
esc_url(), andskip_adminslets administrators keep the normal dashboard.
Customise it
- Clients only: set
'skip_admins' => true. - A page that should not be public: make it private in WordPress. Logged-in editors and admins can still open it inside the frame.
- Different pages per role: return a different
urlfrom thedpc_custom_dashboard_configfilter based oncurrent_user_can(). - Prefer fields over a page builder? See Custom Dashboard with Metabox.
Gotchas
- Pages on another domain usually refuse to be framed (
X-Frame-Optionsorframe-ancestors), and you get an empty frame. Use a page on the same site. - Removing every widget also removes the PHP and browser update nags and Site Health. Keep an eye on Tools → Site Health yourself.
- Pages you navigate to inside the frame without the parameter show the admin bar again. With the base target links leave the frame, so this rarely happens.
- The page is a normal front-end page: caching plugins may cache it. Exclude it from the page cache if it shows user-specific content.
In the Scripts Organizer library Import it with one click from Scripts Organizer.