Skip to main content
Login Join
Snippet · PHP

Add a Custom Dashboard Widget with Quick Links

Shared by Jainil Nagar · June 1, 2026 · @wp_dashboard_setup

25 views
3 upvotes
Back to Snippets
/**
 * Role-aware Quick Links dashboard widget.
 *
 * - Only shows links the current user can actually access
 * - Filterable link list — extend without touching this file
 * - Properly escaped output
 * - WordPress-native styling
 * - Translatable strings
 * - Widget is collapsible/hideable by the user
 *
 * Extend via filter:
 *   add_filter( 'wpfolks_dashboard_quick_links', function( $links ) {
 *       $links[] = [
 *           'label'      => 'My Custom Page',
 *           'url'        => admin_url( 'admin.php?page=my-plugin' ),
 *           'capability' => 'manage_options',
 *           'new_tab'    => false,
 *       ];
 *       return $links;
 *   });
 */


// 1. Register the widget

add_action( 'wp_dashboard_setup', 'wpfolks_register_quick_links_widget' );
function wpfolks_register_quick_links_widget(): void {

    // Only show to users who can access the dashboard meaningfully
    if ( ! current_user_can( 'edit_posts' ) ) return;

    wp_add_dashboard_widget(
        'wpfolks_quick_links',                  // Widget ID
        __( 'Quick Links', 'textdomain' ),       // Title
        'wpfolks_render_quick_links_widget',     // Callback
        null,                                    // Control callback (none)
        null,                                    // Callback args
        'normal',                                // Context: normal | side | column3 | column4
        'high'                                   // Priority: high | core | default | low
    );
}


// 2. Define the full link list

function wpfolks_get_quick_links(): array {
    $links = [
        [
            'label'      => __( 'New Post', 'textdomain' ),
            'url'        => admin_url( 'post-new.php' ),
            'capability' => 'edit_posts',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'New Page', 'textdomain' ),
            'url'        => admin_url( 'post-new.php?post_type=page' ),
            'capability' => 'edit_pages',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'Upload Media', 'textdomain' ),
            'url'        => admin_url( 'media-new.php' ),
            'capability' => 'upload_files',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'Manage Comments', 'textdomain' ),
            'url'        => admin_url( 'edit-comments.php' ),
            'capability' => 'moderate_comments',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'Manage Plugins', 'textdomain' ),
            'url'        => admin_url( 'plugins.php' ),
            'capability' => 'activate_plugins',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'Manage Users', 'textdomain' ),
            'url'        => admin_url( 'users.php' ),
            'capability' => 'list_users',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'General Settings', 'textdomain' ),
            'url'        => admin_url( 'options-general.php' ),
            'capability' => 'manage_options',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'Customizer', 'textdomain' ),
            'url'        => admin_url( 'customize.php' ),
            'capability' => 'customize',
            'new_tab'    => false,
        ],
        [
            'label'      => __( 'View Site', 'textdomain' ),
            'url'        => home_url( '/' ),
            'capability' => 'read',
            'new_tab'    => true,  // External-style — opens in new tab
        ],
    ];

    /**
     * Filter: wpfolks_dashboard_quick_links
     *
     * Add, remove, or reorder links without editing this file.
     * Each link is an array with keys: label, url, capability, and new_tab.
     *
     * @param array $links
     * @return array
     */
    return apply_filters( 'wpfolks_dashboard_quick_links', $links );
}


// 3. Render the widget

function wpfolks_render_quick_links_widget(): void {
    $links = wpfolks_get_quick_links();

    // Filter to only links the current user can access
    $visible = array_filter( $links, function( array $link ): bool {
        $cap = $link['capability'] ?? 'read';
        return current_user_can( $cap );
    } );

    if ( empty( $visible ) ) {
        echo '<p>' . esc_html__( 'No quick links available for your role.', 'textdomain' ) . '</p>';
        return;
    }

    // WordPress-native table styling — consistent with core dashboard widgets
    echo '<table class="widefat striped" role="presentation"><tbody>';

    foreach ( $visible as $link ) {
        $label   = isset( $link['label'] ) ? esc_html( $link['label'] ) : '';
        $url     = isset( $link['url'] )   ? esc_url( $link['url'] )    : '';
        $new_tab = ! empty( $link['new_tab'] );

        if ( ! $label || ! $url ) continue;

        $target = $new_tab ? ' target="_blank" rel="noopener noreferrer"' : '';

        printf(
            '<tr><td><a href="%s"%s>%s%s</a></td></tr>',
            $url,
            $target,
            $label,
            $new_tab
                ? ' <span class="dashicons dashicons-external" style="font-size:14px;vertical-align:middle;" aria-label="' . esc_attr__( 'opens in new tab', 'textdomain' ) . '"></span>'
                : ''
        );
    }

    echo '</tbody></table>';
}
Know a different way to do this? Add your approach as a variation so folks can compare them side by side.
Submit a variation

0 comments