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
| Type | Base Class | Output |
|---|---|---|
| Table | LDDashboardReport_Base | DataTable with CSV/Excel export |
| Chart | LDDashboardChart_Base | Chart.js visualization |
LDDashboardChartBase extends LDDashboardReportBase. Charts inherit all caching, permission, and REST API infrastructure.
LDDashboardReport_Base
Located at includes/reports/class-ld-dashboard-report-base.php.
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)
| Method | Default | Description |
|---|---|---|
get_type(): string | 'table' | Report type identifier. |
filterdatabyrole( array $data, int $userid ): array | Returns data unchanged | Apply role-based row filtering. |
usercanview( int $user_id = 0 ): bool | Checks LDDashboardREST_Permissions | Custom permission logic. |
getcacheduration(): int | HOURINSECONDS | Transient cache TTL in seconds. |
getexportformats(): array | ['csv', 'excel'] | Supported export formats. |
getallowedroles(): array | ['administrator', 'ldinstructor', 'groupleader'] | Roles that can access this report. |
getrestargs(): array | Standard filter params | REST API parameter schema. |
get_meta(): array | Report ID and type | Additional metadata in the response. |
Built-in Infrastructure
The base class handles automatically:
- Caching —
getcacheddata()stores results in WordPress transients. Cache key includes report ID, user ID, and a hash of the query args. - Role scoping —
getaccessiblecourseids()andgetaccessibleuserids()return ID arrays restricted by role (admin gets empty = no restriction). - REST callback —
restcallback( WPRESTRequest $request )enforces permissions, extracts args, and returns aWPREST_Response. - Student scoping — Students are automatically scoped to
userid = currentuser_idin the REST callback.
LDDashboardChart_Base
Located at includes/reports/class-ld-dashboard-chart-base.php.
Extends LDDashboardReportBase. Override gettype() returns 'chart' automatically.
Additional Abstract Methods
| Method | Return | Description |
|---|---|---|
getcharttype(): string | string | Chart.js type: 'bar', 'pie', 'doughnut', or 'line'. |
Additional Override Methods
| Method | Default | Description |
|---|---|---|
formatchartdata( array $data ): array | Expects labels and values keys | Transform raw data to Chart.js dataset format. |
getchartoptions(): array | Responsive, legend top | Chart.js configuration options. |
getfilteroptions(): array | year, month, week | Time period filter options for the frontend. |
getexportformats(): array | [] | Charts do not export by default. |
Registering Reports
Register your report class using LDDashboardReportRegistry::register() inside the lddashboardregisterreports or lddashboardregister_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 doingit_wrong().
REST API Auto-Registration
When you register a report, the REST endpoint is automatically created:
GET /wp-json/ld-dashboard/v2/reports/course-revenue
POST /wp-json/ld-dashboard/v2/reports/course-revenue
DELETE /wp-json/ld-dashboard/v2/reports/course-revenue/cache
Your report’s getrestargs() defines the accepted parameters. The cache clearing endpoint requires administrator access.
JavaScript Auto-Initialization
Render a report container in PHP templates using the registry helpers:
// Table report
echo LD_Dashboard_Report_Registry::render_table( 'course-revenue', array( 'course_id' => 123 ) );
// Chart
echo LD_Dashboard_Report_Registry::render_chart( 'revenue-trend', array( 'filter' => 'month' ) );
// Global function aliases
echo 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:
The LDReportManager JavaScript module detects these attributes and initializes DataTables or Chart.js automatically.
Complete Example: Course Revenue Report
'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=123
Filtering Report Data
Use the lddashboardreport_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 );
