Creating Custom Reports
LearnDash Dashboard provides a unified reports architecture since version 7.5.0. You can create custom table reports and Chart.js charts that integrate with the REST API, DataTables, and access control system.
Report Types
Section titled “Report Types”| Type | Base Class | Output |
|---|---|---|
| Table | LD_Dashboard_Report_Base |
DataTable with CSV/Excel export |
| Chart | LD_Dashboard_Chart_Base |
Chart.js visualization |
LD_Dashboard_Chart_Base extends LD_Dashboard_Report_Base. Charts inherit all caching, permission, and REST API infrastructure.
LD_Dashboard_Report_Base
Section titled “LD_Dashboard_Report_Base”Located at includes/reports/class-ld-dashboard-report-base.php.
Abstract Methods (required)
Section titled “Abstract Methods (required)”| Method | Return | Description |
|---|---|---|
get_id(): string |
string |
Unique identifier (e.g., my-revenue-report). Used for REST endpoint and cache keys. |
get_title(): string |
string |
Human-readable translated title. |
get_columns(): array |
array |
DataTables column definitions. |
fetch_data( array $args ): array |
array |
Raw data fetch. Do not apply role filtering here. |
Override Methods (optional)
Section titled “Override Methods (optional)”| Method | Default | Description |
|---|---|---|
get_type(): string |
'table' |
Report type identifier. |
filter_data_by_role( array $data, int $user_id ): array |
Returns data unchanged | Apply role-based row filtering. |
user_can_view( int $user_id = 0 ): bool |
Checks LD_Dashboard_REST_Permissions |
Custom permission logic. |
get_cache_duration(): int |
HOUR_IN_SECONDS |
Transient cache TTL in seconds. |
get_export_formats(): array |
['csv', 'excel'] |
Supported export formats. |
get_allowed_roles(): array |
['administrator', 'ld_instructor', 'group_leader'] |
Roles that can access this report. |
get_rest_args(): array |
Standard filter params | REST API parameter schema. |
get_meta(): array |
Report ID and type | Additional metadata in the response. |
Built-in Infrastructure
Section titled “Built-in Infrastructure”The base class handles automatically:
- Caching —
get_cached_data()stores results in WordPress transients. Cache key includes report ID, user ID, and a hash of the query args. - Role scoping —
get_accessible_course_ids()andget_accessible_user_ids()return ID arrays restricted by role (admin gets empty = no restriction). - REST callback —
rest_callback( WP_REST_Request $request )enforces permissions, extracts args, and returns aWP_REST_Response. - Student scoping — Students are automatically scoped to
user_id = current_user_idin the REST callback.
LD_Dashboard_Chart_Base
Section titled “LD_Dashboard_Chart_Base”Located at includes/reports/class-ld-dashboard-chart-base.php.
Extends LD_Dashboard_Report_Base. Override get_type() returns 'chart' automatically.
Additional Abstract Methods
Section titled “Additional Abstract Methods”| Method | Return | Description |
|---|---|---|
get_chart_type(): string |
string |
Chart.js type: 'bar', 'pie', 'doughnut', or 'line'. |
Additional Override Methods
Section titled “Additional Override Methods”| Method | Default | Description |
|---|---|---|
format_chart_data( array $data ): array |
Expects labels and values keys |
Transform raw data to Chart.js dataset format. |
get_chart_options(): array |
Responsive, legend top | Chart.js configuration options. |
get_filter_options(): array |
year, month, week |
Time period filter options for the frontend. |
get_export_formats(): array |
[] |
Charts do not export by default. |
Registering Reports
Section titled “Registering Reports”Register your report class using LD_Dashboard_Report_Registry::register() inside the ld_dashboard_register_reports or ld_dashboard_register_charts action hook.
add_action( 'ld_dashboard_register_reports', function( $registry ) { LD_Dashboard_Report_Registry::register( 'course-revenue', 'My_Course_Revenue_Report' );} );
add_action( 'ld_dashboard_register_charts', function( $registry ) { LD_Dashboard_Report_Registry::register( 'revenue-trend', 'My_Revenue_Trend_Chart' );} );register() instantiates your class and validates it extends the correct base. If the class does not exist or extends an incorrect base, it calls _doing_it_wrong().
REST API Auto-Registration
Section titled “REST API Auto-Registration”When you register a report, the REST endpoint is automatically created:
GET /wp-json/ld-dashboard/v2/reports/course-revenuePOST /wp-json/ld-dashboard/v2/reports/course-revenueDELETE /wp-json/ld-dashboard/v2/reports/course-revenue/cacheYour report’s get_rest_args() defines the accepted parameters. The cache clearing endpoint requires administrator access.
JavaScript Auto-Initialization
Section titled “JavaScript Auto-Initialization”Render a report container in PHP templates using the registry helpers:
// Table reportecho LD_Dashboard_Report_Registry::render_table( 'course-revenue', array( 'course_id' => 123 ) );
// Chartecho LD_Dashboard_Report_Registry::render_chart( 'revenue-trend', array( 'filter' => 'month' ) );
// Global function aliasesecho ld_dashboard_render_table( 'course-revenue' );echo ld_dashboard_render_chart( 'revenue-trend' );The rendered HTML uses data-ld-report or data-ld-chart attributes for JavaScript auto-initialization:
<!-- Table --><div class="ld-dashboard-report-table-wrapper"> <table id="ld-report-table-course-revenue" class="ld-dashboard-table display ld-dashboard-datatable" data-ld-report="course-revenue" data-options='{"course_id":123}' cellspacing="0" width="100%"> </table></div>
<!-- Chart --><div class="ld-dashboard-chart-wrapper"> <div id="ld-report-chart-revenue-trend" class="ld-dashboard-chart-js" data-ld-chart="revenue-trend" data-options='{}'> </div></div>The LDReportManager JavaScript module detects these attributes and initializes DataTables or Chart.js automatically.
Complete Example: Course Revenue Report
Section titled “Complete Example: Course Revenue Report”<?php/** * Custom Course Revenue Report. * * @since 1.0.0 */class My_Course_Revenue_Report extends LD_Dashboard_Report_Base {
/** * Get report ID. * * @since 1.0.0 * @return string */ public function get_id(): string { return 'course-revenue'; }
/** * Get report title. * * @since 1.0.0 * @return string */ public function get_title(): string { return __( 'Course Revenue', 'my-plugin' ); }
/** * Get column definitions. * * @since 1.0.0 * @return array */ protected function get_columns(): array { return array( array( 'data' => 'course_name', 'title' => __( 'Course', 'my-plugin' ), 'visible' => true, 'orderable' => true, ), array( 'data' => 'sales', 'title' => __( 'Sales', 'my-plugin' ), 'visible' => true, 'orderable' => true, ), array( 'data' => 'revenue', 'title' => __( 'Revenue', 'my-plugin' ), 'visible' => true, 'orderable' => true, ), ); }
/** * Fetch revenue data. * * @since 1.0.0 * @param array $args Query arguments. * @return array */ protected function fetch_data( array $args ): array { global $wpdb;
$course_id = isset( $args['course_id'] ) ? absint( $args['course_id'] ) : 0;
// Example: join commission logs to course data. $query = $wpdb->prepare( "SELECT p.post_title AS course_name, COUNT(*) AS sales, SUM(l.course_price) AS revenue FROM {$wpdb->prefix}ld_dashboard_instructor_commission_logs l INNER JOIN {$wpdb->posts} p ON p.ID = l.course_id WHERE p.post_status = 'publish' " . ( $course_id ? $wpdb->prepare( 'AND l.course_id = %d', $course_id ) : '' ) . " GROUP BY l.course_id ORDER BY revenue DESC", // Note: prepare() args are only needed if course_id filter is active. ...( $course_id ? array( $course_id ) : array() ) );
return $wpdb->get_results( $query, ARRAY_A ) ?: array(); }
/** * Filter data by role — instructors see only their own courses. * * @since 1.0.0 * @param array $data Raw data. * @param int $user_id User ID. * @return array */ protected function filter_data_by_role( array $data, int $user_id ): array { // Admins see all. if ( learndash_is_admin_user( $user_id ) ) { return $data; }
$accessible_ids = $this->get_accessible_course_ids( $user_id );
if ( empty( $accessible_ids ) ) { return array(); }
return array_filter( $data, fn( $row ) => in_array( $row['course_id'], $accessible_ids, true ) ); }
/** * Override cache duration to 30 minutes. * * @since 1.0.0 * @return int */ protected function get_cache_duration(): int { return 30 * MINUTE_IN_SECONDS; }}
// Register the report.add_action( 'ld_dashboard_register_reports', function() { LD_Dashboard_Report_Registry::register( 'course-revenue', 'My_Course_Revenue_Report' );} );Then render in a template:
// Render in a tab template or custom page.echo ld_dashboard_render_table( 'course-revenue', array( 'course_id' => 0 ) );Access the data via REST:
GET /wp-json/ld-dashboard/v2/reports/course-revenue?course_id=123Filtering Report Data
Section titled “Filtering Report Data”Use the ld_dashboard_report_data filter to modify any report’s data after role filtering:
add_filter( 'ld_dashboard_report_data', function( $data, $report_id, $args ) { if ( 'course-revenue' === $report_id ) { // Format revenue as currency. foreach ( $data as &$row ) { $row['revenue'] = '$' . number_format( (float) $row['revenue'], 2 ); } } return $data;}, 10, 3 );Use the report-specific filter for targeted changes:
add_filter( 'ld_dashboard_report_course-revenue_data', function( $response, $args ) { // Modify the full response array (columns, data, meta, etc.) return $response;}, 10, 2 );
