At the time of writing and the time of embarking on this project, there was NO documented way to add additional tabs and content to the SureCart Dashboard. It had been done by the excellent SurelyWP team, but it wasn’t documented. I have added this here as I hope my plea for a unified, easy hook and filter solution that is direct from SureCart is responded to and we don’t risk an unruly mess of custom templates! – If this changes, I will update this header – Until we can all use this and if we follow the rules and use the “framework” we wont break other peoples stuff
So, on to the good stuff: how to add those tabs to the dashboard.
WordPress comes with functionality to allow this to happen; it’s just not currently enabled in the dashboard template. Thankfully, we have the ability to switch a template, too, and that’s what we are going to do. We are going to switch the default or the SurelyWP template with one of our own. We are going to use Hooks & Filters. These are common developer tools, and SureCart already comes with a few of these, just not for this particular item.
So I’ve taken the default Surecart template and added some filters to it that allow anyone to hook right in using a customised action hook with a prefix of rup_sc_dashextender_
This is totally reusable, and it doesn’t matter how many people are using it and the namespace as long as it’s running this template and you’re not using the same “content” or function names. This is why I use the prefix rup_xxxx_xxxx in most cases.
Checking my SurelyWP plugins, I could see this is how Nelson and his team had done it this way too it makes sense its WordPress naive, so to be backwards compatible and not break my own surely plugins, after all I use support port for surecart on Reallyusefulplugins.com, I have added there hooks and filters in so if you’ve got an addon for SurelyWP and it uses there custom filters it will work with this template too (I hope, I can’t possibly test them all) but this is a public repo so you can just test your code and push me a change. If it doesn’t break the filter(s) and hook(s) it will be committed. This is community spirit for users, not gatekeeping or my code first (although my code needs to be the template running for this to work)
So the first thing i did was check what template is loading using this plugin. This will be useful if your using this framework and your changes are not showing if it doesn’t show the frameworks my-surecart-dashboard.php we are not successfully changing the template so how do we do that?

Dashboard
Firstly note we want “Larry” & “Joe” to be able to both use this framework and that is difficult if we are all using different files, so we need to conditionally loading our files. The way we do that is by keeping everything within our namespace and globally declaring a statement as true in this case the below will not run at all if someone else has already run and included the template file we wil just be able to “hook in” to what they’ve loaded but if we are first or the only it will load ours.
So in our main plugin file or a subfile we would add the following, the location on line 12 can be changes this isn’t important whats important is we are only chaning the template once and we are all just using what is there and we are NOT changing the template file unless its in the main repo, for this to work we all need to be able to understand what to inspect.
if ( ! defined( 'RUPDASHEXTENDERSC_TEMPLATE_LOADED' ) ) {
define( 'RUPDASHEXTENDERSC_TEMPLATE_LOADED', true );
/**
* Override the SureCart dashboard template.
*
* @param string $template The original template file.
* @return string The modified template file if condition is met.
*/
function override_surecart_dashboard_template( $template ) {
// Adjust 'customer-dashboard' to match your dashboard page's slug if needed.
$custom_template = plugin_dir_path( __FILE__ ) . 'templates/my-surecart-dashboard.php';
if ( file_exists( $custom_template ) ) {
return $custom_template;
}
return $template;
}
add_filter( 'template_include', __NAMESPACE__ . '\\override_surecart_dashboard_template', 9999 );
}
PHPThe above will run once and give us the access we need to add our content
So now Larry and Joe can both load the file if needed without causing issues, the next thing they need to do is add there custom tab in my example we are going to load a normal guttenberg page called dashboard-content
So elsewhere in my plugin or snippet where I want to add a tab and content I can now be sure the custom template is avaliable to me and I can check that in testing using the plugin above and the response (or viewing that template in the file system)
So now we need to add our tab thats easy our template has a new filter in it so we will just use that
add_filter( 'rup_sc_dashextender_surecart_navigation', 'my_custom_dashboard_navigation_custom_content', 10, 3 );
function my_custom_dashboard_navigation_custom_content( $navigation, $data, $controller ) {
$navigation['custom_content'] = [
'name' => __( 'Custom Content XXX', 'my-surecart-dashboard' ),
'icon_name' => 'my-custom-icon', // Use your desired icon name (make sure your CSS targets this)
'href' => add_query_arg( 'sc-page', 'custom_content', get_permalink() ),
];
return $navigation;
}
PHPSo in this our adding our custom tab and its name and the sc-page and the content
Now we need to add our content to that tab and below is how we do that using add action
Shortcode:
add_action( 'rup_sc_dashextender_surecart_dashboard_right_custom_content', 'my_custom_dashboard_tab_content_custom', 10, 2 );
function my_custom_dashboard_tab_content_custom( $controller, $data ) {
// Retrieve the page by its slug (for example, 'custom-content-page')
$page = get_page_by_path( 'dashboard-content' );
if ( $page ) {
echo do_shortcode( '[big_red]' );
} else {
echo '<div class="my-custom-tab-content">';
echo '<h2>' . __( 'Content Not Found', 'my-surecart-dashboard' ) . '</h2>';
echo '<p>' . __( 'Please create a page with the slug "custom-content-page" to manage your custom content.', 'my-surecart-dashboard' ) . '</p>';
echo '</div>';
}
}
PHPA Page (dashboad-content)
add_action( 'rup_sc_dashextender_surecart_dashboard_right_custom_content', 'my_custom_dashboard_tab_content_custom', 10, 2 );
function my_custom_dashboard_tab_content_custom( $controller, $data ) {
// Retrieve the page by its slug (for example, 'custom-content-page')
$page = get_page_by_path( 'dashboard-content' );
if ( $page ) {
$content = apply_filters( 'the_content', $page->post_content );
echo '<div class="my-custom-tab-content">' . $content . '</div>';
} else {
echo '<div class="my-custom-tab-content">';
echo '<h2>' . __( 'Content Not Found', 'my-surecart-dashboard' ) . '</h2>';
echo '<p>' . __( 'Please create a page with the slug "custom-content-page" to manage your custom content.', 'my-surecart-dashboard' ) . '</p>';
echo '</div>';
}
}
PHPKey Points to understand
Consistency is Crucial:
- Filter and Action Names: The names (like
rup_sc_dashextender_surecart_navigation
andrup_sc_dashextender_dashboard_right
) must be consistent between the template and your custom functions. Any deviation will mean your custom code won’t be recognized. – See the naming convention this only works if we are consistant and all don’t edit the template!ThiThis It - Parameter Consistency: Ensure that the number and order of parameters in your custom functions match those expected by the hooks.
- Filter and Action Names: The names (like
Naming Conventions:
- Were using a reusable framework, don’t adjust filters or actions they will stop others from working so please use the
rup_sc_dashextender
naming convention and load the default template it should work for everyone! - The keys in the navigation array (
name
,icon_name
,href
) are expected to be in the correct format so that the dashboard template can properly render the custom tab.
- Were using a reusable framework, don’t adjust filters or actions they will stop others from working so please use the
Consistency:
When customizing your dashboard, consistency in the identifiers is key. For example, in your code the identifier is custom_content. Here’s what needs to stay consistent:
Navigation Key:
In your navigation array, you’re adding a key named'custom_content'
. This key must match the identifier used in the query parameter (set byadd_query_arg( 'sc-page', 'custom_content', get_permalink() )
)Action Hook Suffix:
The custom action hook is namedrup_sc_dashextender_surecart_dashboard_right_custom_content
. Notice that the ending custom_content matches the navigation key. This pairing ensures that when the user clicks the tab (which setssc-page=custom_content
), the correct action hook is triggered to load the corresponding content.Overall Identifier:
Using the same identifier custom_content in both the filter (navigation creation) and the action (content display) ties the two together. If you change one side (e.g., the navigation key), you must update the corresponding query parameter and action hook name so that they all refer to the same identifier.
What Stays the Same:
- Filter Name:
The filter hook used in the template (e.g.,apply_filters( 'rup_sc_dashextender_surecart_navigation', ... )
) must match exactly with the filter name used when you add your custom function viaadd_filter()
.
Example:
Just like that we have a working dashboard link and it can load our shortcode, our page or some hardcoded html!
I have copied and pasted below some information from the docs for this repo to assist you with doing this and understaning this but until there is an offical solution this is the best methodology that allows us all to play nice in my opinion.
Below is the documentation
Navigation Customization
// In our dashboard template:
$data['navigation'] = apply_filters( 'rup_sc_dashextender_surecart_navigation', $data['navigation'], $data, $controller );
This needs to be consistent to hook into the filters there are no changes needed to the enclosed template it just works.
Hooking in
Parameters Order and Count:
When you hook into this filter, ensure that the number and order of parameters match the original declaration. In this case, the parameters are the navigation array, the data array, and the controller object.
Example:
add_filter( 'rup_sc_dashextender_surecart_navigation', 'my_custom_dashboard_navigation', 10, 3 );
function my_custom_dashboard_navigation( $navigation, $data, $controller ) {
// Add your custom tab.
$navigation['custom_tab'] = [
'name' => __( 'Custom Tab', 'my-surecart-dashboard' ),
'icon_name' => 'custom-icon',
'href' => add_query_arg( 'sc-page', 'custom_tab', get_permalink() ),
];
return $navigation;
}
PHPNavigation Array Structure:
The custom tab you add should have specific keys:
name
: The display name of the tab.icon_name
: The identifier for the icon.href
: The URL (often including a query parameter) that points to your custom content.
These keys are expected by the dashboard template for consistent rendering.
Loading Custom Content
The dashboard template checks the URL for the query parameter (e.g., ?sc-page=custom_tab
) and loads content accordingly. You have three common options to render this content:
Option 1: Custom HTML Output
Action Hook Name:
You need to use the same action hook (e.g.,rup_sc_dashextender_surecart_dashboard_right
) provided by the dashboard template.Direct Output:
Your function directly echoes HTML content.
add_action( 'rup_sc_dashextender_surecart_dashboard_right', 'my_custom_dashboard_tab_content_html', 10, 2 );
function my_custom_dashboard_tab_content_html( $controller, $data ) {
echo '<div class="my-custom-tab-content">';
echo '<h2>' . __( 'Custom HTML Content', 'my-surecart-dashboard' ) . '</h2>';
echo '<p>This is hard-coded content for the custom tab.</p>';
echo '</div>';
}
PHPOption 2: Loading a WordPress Page
Page Slug:
A page with a specific slug (e.g.,dashboard-content
) should exist. The function retrieves this page and processes its content.Consistency in Slug:
The slug used inget_page_by_path( 'dashboard-content' )
must match the actual page slug.
add_action( 'rup_sc_dashextender_surecart_dashboard_right', 'my_custom_dashboard_tab_content_page', 10, 2 );
function my_custom_dashboard_tab_content_page( $controller, $data ) {
$page = get_page_by_path( 'dashboard-content' );
if ( $page ) {
$content = apply_filters( 'the_content', $page->post_content );
echo '<div class="my-custom-tab-content">';
echo $content;
echo '</div>';
} else {
echo '<div class="my-custom-tab-content">';
echo '<h2>' . __( 'Content Not Found', 'my-surecart-dashboard' ) . '</h2>';
echo '<p>Please create a page with the slug "dashboard-content".</p>';
echo '</div>';
}
}
PHP- Option 3: Rendering a Shortcode (preferred & easiest option)
Shortcode Usage:
You can render content through a shortcode usingdo_shortcode()
. Make sure the shortcode exists and is properly registered.
Example:
add_action( 'rup_sc_dashextender_surecart_dashboard_right', 'my_custom_dashboard_tab_content_shortcode', 10, 2 );
function my_custom_dashboard_tab_content_shortcode( $controller, $data ) {
echo '<div class="my-custom-tab-content">';
echo do_shortcode( '[my_shortcode]' );
echo '</div>';
}
PHPKey Points to understand
Consistency is Crucial:
- Filter and Action Names: The names (like
rup_sc_dashextender_surecart_navigation
andrup_sc_dashextender_dashboard_right
) must be consistent between the template and your custom functions. Any deviation will mean your custom code won’t be recognized. - Parameter Consistency: Ensure that the number and order of parameters in your custom functions match those expected by the hooks.
- Filter and Action Names: The names (like
Naming Conventions:
- Were using a reusable framework, don’t adjust filters or actions they will stop others from working so please use the
rup_sc_dashextender
naming convention and load the default template it should work for everyone! - The keys in the navigation array (
name
,icon_name
,href
) are expected to be in the correct format so that the dashboard template can properly render the custom tab.
- Were using a reusable framework, don’t adjust filters or actions they will stop others from working so please use the
Consistency:
When customizing your dashboard, consistency in the identifiers is key. For example, in your code the identifier is custom_content. Here’s what needs to stay consistent:
Navigation Key:
In your navigation array, you’re adding a key named'custom_content'
. This key must match the identifier used in the query parameter (set byadd_query_arg( 'sc-page', 'custom_content', get_permalink() )
).Action Hook Suffix:
The custom action hook is namedrup_sc_dashextender_surecart_dashboard_right_custom_content
. Notice that the ending custom_content matches the navigation key. This pairing ensures that when the user clicks the tab (which setssc-page=custom_content
), the correct action hook is triggered to load the corresponding content.Overall Identifier:
Using the same identifier custom_content in both the filter (navigation creation) and the action (content display) ties the two together. If you change one side (e.g., the navigation key), you must update the corresponding query parameter and action hook name so that they all refer to the same identifier.
This consistency is what allows the system to correctly match the navigation item with its intended content area.
Content Loading Options:
- Direct HTML: Best when content is static and doesn’t need to be edited.
- WordPress Page: Ideal for content that you want site editors to manage.
- Shortcode Rendering: Good for dynamic or reusable content and the best option for plugin developers, you could use if statements or other options to only render the types of pages you need.