Creating Custom Reports

Get Started

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

TypeBase ClassOutput
TableLDDashboardReport_BaseDataTable with CSV/Excel export
ChartLDDashboardChart_BaseChart.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)

MethodReturnDescription
get_id(): stringstringUnique identifier (e.g., my-revenue-report). Used for REST endpoint and cache keys.
get_title(): stringstringHuman-readable translated title.
get_columns(): arrayarrayDataTables column definitions.
fetch_data( array $args ): arrayarrayRaw data fetch. Do not apply role filtering here.

Override Methods (optional)

MethodDefaultDescription
get_type(): string'table'Report type identifier.
filterdatabyrole( array $data, int $userid ): arrayReturns data unchangedApply role-based row filtering.
usercanview( int $user_id = 0 ): boolChecks LDDashboardREST_PermissionsCustom permission logic.
getcacheduration(): intHOURINSECONDSTransient cache TTL in seconds.
getexportformats(): array['csv', 'excel']Supported export formats.
getallowedroles(): array['administrator', 'ldinstructor', 'groupleader']Roles that can access this report.
getrestargs(): arrayStandard filter paramsREST API parameter schema.
get_meta(): arrayReport ID and typeAdditional metadata in the response.

Built-in Infrastructure

The base class handles automatically:

  • Cachinggetcacheddata() stores results in WordPress transients. Cache key includes report ID, user ID, and a hash of the query args.
  • Role scopinggetaccessiblecourseids() and getaccessibleuserids() return ID arrays restricted by role (admin gets empty = no restriction).
  • REST callbackrestcallback( WPRESTRequest $request ) enforces permissions, extracts args, and returns a WPREST_Response.
  • Student scoping — Students are automatically scoped to userid = currentuser_id in 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

MethodReturnDescription
getcharttype(): stringstringChart.js type: 'bar', 'pie', 'doughnut', or 'line'.

Additional Override Methods

MethodDefaultDescription
formatchartdata( array $data ): arrayExpects labels and values keysTransform raw data to Chart.js dataset format.
getchartoptions(): arrayResponsive, legend topChart.js configuration options.
getfilteroptions(): arrayyear, month, weekTime 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 );
Last updated: March 4, 2026