/ Developers

Hook into every report your clients run.

Get a call when a report is saved, run or exported, when a scheduled email goes out or fails, and when a bulk change finishes or is undone. Add your own columns and custom-field plugins. All of it from a plugin or mu-plugin, using ordinary WordPress hooks.

The same guide ships inside the plugin as DEVELOPERS.md. Every hook listed here is checked against the code by an automated test.

mu-plugins/report-alerts.php
// Post to Slack when a scheduled report doesn't go out.
add_action( 'vrb_schedule_failed', function ( $report_id, $schedule, $error ) {
	wp_remote_post( MY_SLACK_WEBHOOK_URL, [
		'headers' => [ 'Content-Type' => 'application/json' ],
		'body'    => wp_json_encode( [
			'text' => sprintf( 'Report "%s" was not sent: %s',
				get_the_title( $report_id ),
				$error->get_error_message() ),
		] ),
	] );
}, 10, 3 );
10 Beta
Lifecycle actions
Save, run, export, schedule, bulk, undo
25 Stable
Filters
Data, limits, exports, permissions, tuning
2 Stable
Extension contracts
Virtual columns and field sources

WP Report Studio — developer guide

How to extend WP Report Studio from your own plugin, theme or mu-plugin without modifying it: add data sources and columns, react to report activity, and tune behaviour. Everything here is an ordinary WordPress hook or a small PHP contract.

A complete, working example plugin is reproduced in Example extension. It is also available as an installable zip (wrs-example-extension.zip) from the Developers page of the WP Report Studio website.

Compatibility policy

Level Meaning
Stable Supported public API. Not removed or changed incompatibly in a minor release. Deprecations are announced in the changelog and kept working (with a _doing_it_wrong / do_action_deprecated notice) for at least two minor releases.
Beta Supported and tested, but the shape may still change before 1.0 in response to developer feedback. New data is added to the end of argument lists or inside $context arrays, never reordered.
Tuning Performance escape hatches. Supported, but the default may change and the hook may be retired when the underlying query strategy changes.
Internal Not an API. Listed only so you know not to depend on it.

Anything not listed in this guide (classes, methods, option names, table layouts, AJAX actions, JavaScript globals) is internal and can change in any release.

Every hook name starts with vrb_. All PHP API is available after plugins_loaded.

Lifecycle actions (Beta)

Fired after the outcome is final, so a listener can record or react but can’t veto. Use filters to change behaviour. Several of these run from WP-Cron or WP-CLI, so don’t assume a browser request or a logged-in user. If a listener throws, WP Report Studio catches and logs it (Report Studio → Diagnostics), the download or job carries on, and listeners after it on that action don’t run. Keep listeners fast; push slow work (HTTP calls) to a background job.

Action Arguments When
vrb_report_saved int $report_id, array $definition, bool $is_new A report is created (builder, duplicate, template, AI suggestion) or its definition/name saved. The builder autosaves, so this fires on most edits; defer heavy work.
vrb_report_ran int $report_id, array $definition, array $context A saved report returned rows in the report viewer: each load, page, filter or sort change. $context: total, rows, page, per_page, duration_ms, user_id. $definition includes the viewer’s temporary filter/sort changes. Not fired for the builder’s live preview.
vrb_report_run_failed int $report_id, WP_Error $error, array $context A viewer run hit a database error (run_sql_error) or exception (run_exception). Not fired for permission errors, bad input or the Free-plan Pro gate. $context: user_id.
vrb_export_completed int $report_id, string $format, int $rows, array $context A CSV or XLSX export finished. $format: csv/xlsx. $rows excludes the header. $context['destination']: download or file (scheduled email attachment; $context['path'] is the temp file, deleted after sending).
vrb_export_failed int $report_id, string $format, WP_Error $error, array $context An export could not be produced. Codes: not_configured, too_many_rows, open_failed, no_openspout.
vrb_schedule_sent int $report_id, array $schedule, array $recipients, string $format A scheduled report was emailed (Pro).
vrb_schedule_failed int $report_id, array $schedule, WP_Error $error A scheduled delivery failed (Pro). Codes: license_required, not_found, mail_failed, plus the export codes above. $schedule is [] if the report no longer exists.
vrb_bulk_job_completed int $log_id, array $job A bulk action finished, synchronously or as a background job (Pro). $job: action_type, action_label, executed_by, target_count, total_targets, status. $log_id is the Undo handle.
vrb_bulk_job_failed int $log_id, array $job A background bulk job stopped early: status is failed or cancelled (Pro). Fired once per transition.
vrb_bulk_job_undone int $log_id, int $user_id A bulk action was undone (Pro).
add_action( 'vrb_schedule_failed', function ( $report_id, $schedule, $error ) {
	wp_mail( get_option( 'admin_email' ), 'Scheduled report failed',
		get_the_title( $report_id ) . ': ' . $error->get_error_message() );
}, 10, 3 );

Filters (Stable)

Data and discovery

Filter Value → return Arguments Use it to
vrb_reportable_post_types array of name => { name, label, description, count } — Add or remove post types offered as a report’s root. Plugin-configuration types (ACF field groups, form definitions, …) and WooCommerce HPOS order placeholders are removed by default.
vrb_query_post_statuses string[] (default publish, draft, private) — Change which post statuses reports include. trash is always excluded.
vrb_field_sources VRB_Field_Source[] — Register a custom-fields plugin integration. See Field sources.
vrb_virtual_providers string[] class names — Add computed or external-table columns. See Virtual columns.
vrb_extra_field_nodes array of field nodes (default []) string $post_type, array $claimed_keys Add plain postmeta-backed fields to the field tree (e.g. keys the sampler misses). Each node needs type and key; see Field nodes.
vrb_known_analytics_meta array meta_key => [ label, data_type, source_name ] — Label view/like counters stored in postmeta so they appear as named fields.
vrb_repeater_max_size int (default 50) string $repeater_key Raise how many rows of an ACF repeater are expanded.
vrb_default_sort_newest_first bool (default true) — false to order unsorted reports oldest-first.

Limits and caching

Filter Value → return Arguments Use it to
vrb_query_max_rows int (default 50,000) — Safety cap for exports and scheduled deliveries; past it they fail with too_many_rows instead of exhausting memory. 0 disables the cap.
vrb_query_cache_ttl int seconds (default 30) — How long identical report queries are reused (view → export). 0 disables.
vrb_run_all_rows_threshold int (default 500) int $report_id, array $definition Up to this many rows, the viewer loads the whole result once and pages in the browser.

Exports

Filter Value → return Arguments Use it to
vrb_export_source_column_label string (default “Source”) — Rename the optional edit-link column in CSV/XLSX.
vrb_csv_write_utf8_bom bool (default true) array $definition Drop the UTF-8 byte-order mark for tools that dislike it (Excel needs it for non-ASCII text).
vrb_csv_formula_guard bool (default true) — Disable the spreadsheet formula-injection guard: non-numeric text starting with =, +, - or @, or with a tab or carriage return, gets a leading '. Only for trusted data. Applies to CSV; Excel downloads always store such text as text.
vrb_csv_excel_long_id_guard bool (default true) — Disable the guard that wraps long all-digit text values (IDs, phone numbers) as ="…" so Excel doesn’t turn them into scientific notation.
vrb_excel_wrap_chars int (default 60) — Characters per wrapped line assumed when estimating how tall an Excel cell with several details per line gets. Used only for the pre-download size warning.
vrb_excel_row_lines int (default 27) — Lines an Excel row can show (rows max out at 409 points). Cells estimated taller trigger the pre-download warning; the file itself is never cut.

Permissions and bulk actions

Filter Value → return Arguments Use it to
vrb_grant_caps_via_edit_posts bool (default true) — false to stop edit_posts implying all Report Studio capabilities, so access comes only from the vrb_* capabilities. See Capabilities.
vrb_bulk_required_post_caps string[] meta capabilities string $action_slug, array $params Per-post capabilities a user needs for a bulk action to touch a post (default edit_post; delete_post for trash; plus publish_post when publishing). Posts that fail are skipped and reported.
vrb_bulk_sync_threshold int (default 100) string $action_slug, int[] $ids, array $params Above this many posts, a bulk action runs as a background job.
vrb_bulk_chunk_size int (default 25) string $action_slug Posts per background-job chunk.

Tuning

Filter Value → return Arguments Use it to
vrb_query_select_hint string (STRAIGHT_JOIN on MySQL, '' on SQLite) array $definition Remove or replace the join-order hint if your MySQL version plans badly with it.
vrb_query_use_two_phase bool array $definition Force or prevent the two-phase (IDs, then hydrate) fetch for complex reports.
vrb_consolidate_meta_joins bool (default true) string $scope_key, string $post_id_expr Turn off the single-join postmeta optimisation for a scope while diagnosing a query.
vrb_install_postmeta_composite_index bool (default true) — false to stop the plugin adding its (post_id, meta_key) index to wp_postmeta (some managed hosts disallow ALTER TABLE).

Internal (not supported)

Hook Why it isn’t an API
vrb_upgrade_url Commercial upgrade links; may change with licensing.
vrb_trial_url Commercial trial links.
vrb_founders_coupons Launch pricing.
vrb_checkout_coupon Launch pricing.
vrb_show_kpi_cards Unfinished feature toggle.

Extension contracts

Virtual columns

A virtual provider adds fields whose values come from SQL you supply. They appear in the field tree, and sort, filter, total and export like any other field. Providers are classes with four static methods, registered by class name through vrb_virtual_providers:

Method Returns
is_available() bool. Return false to hide the fields (e.g. your table isn’t installed).
fields( string $post_type ) Array of field nodes with type => 'virtual', provider => YourClass::class, a key, label and data_type.
sql_join( string $field_key ) LEFT JOIN … clause(s), or '' when the expression needs no join. Use a table alias unique to your plugin.
sql_expr( string $field_key ) SQL expression for the value. The report’s root post is p_root; rows are grouped by p_root.ID, so aggregate joined rows (SUM(...), MAX(...)) or use a correlated subquery.

This SQL is compiled into the report query as trusted code. Build it only from constants and $wpdb->prefix; never interpolate request data or field values. Filters a user applies to the column are escaped by WP Report Studio.

Field sources

To integrate a custom-fields plugin (the way ACF, Meta Box and Pods are supported), implement VRB_Field_Source and add an instance through vrb_field_sources:

interface VRB_Field_Source {
	public function id();         // 'my-fields'
	public function is_active();  // true only when your plugin is loaded
	public function discover( $post_type, array &$tree, array &$claimed_keys, array &$repeater_roots );
}

discover() appends field nodes for $post_type to $tree, marks the meta keys it owns in $claimed_keys (key => true) so the raw postmeta scan doesn’t list them twice, and lists repeater root keys in $repeater_roots. Postmeta-stored fields need no query code. Data in a custom table or join table is described with a storage descriptor; follow includes/class-source-pods.php, the reference implementation for both. Field sources are Stable as an interface; the storage descriptor shape is Beta.

Field nodes

A field node is an array:

Key Required Notes
type yes meta (postmeta), virtual, relationship, repeater.
key yes Meta key, or your virtual field key.
label recommended Shown in the builder.
data_type recommended text, number, date, boolean, email, url, attachment, user, taxonomy_ref. Drives filters, totals and formatting.
choices optional value => label for select-style fields; shown as labels and offered as filter values.
provider virtual only Your provider class name.

Capabilities

Access is controlled by five capabilities (constants on VRB_Capabilities): vrb_view_reports (VIEW), vrb_edit_reports (EDIT), vrb_export_reports (EXPORT), vrb_run_bulk_actions (BULK), vrb_manage_schedules (SCHEDULE). Grant them to custom roles with add_cap(). By default edit_posts implies all five; turn that off with vrb_grant_caps_via_edit_posts.

Custom bulk actions

Not yet a supported API. The built-in actions use an internal registry whose snapshot/undo contract is still changing; if you need one, tell us what it should do.

Example extension

The example adds an “Approved comments” column to every post type, records lifecycle activity (inspect it with wp wrs-example activity), and includes scheduled posts in reports.

<?php
/**
 * Plugin Name: WP Report Studio — Example Extension
 * Requires Plugins: wp-report-studio
 */

class WRS_Example_Comment_Count_Provider {
	public static function is_available() {
		return true;
	}

	public static function fields( $post_type ) {
		return array(
			array(
				'type'      => 'virtual',
				'provider'  => __CLASS__,
				'key'       => 'approved_comments',
				'label'     => 'Approved comments',
				'data_type' => 'number',
			),
		);
	}

	public static function sql_join( $field_key ) {
		return '';
	}

	public static function sql_expr( $field_key ) {
		global $wpdb;
		return "(SELECT COUNT(*) FROM {$wpdb->comments} wrs_c WHERE wrs_c.comment_post_ID = p_root.ID AND wrs_c.comment_approved = '1')";
	}
}

add_filter( 'vrb_virtual_providers', function ( $providers ) {
	$providers[] = 'WRS_Example_Comment_Count_Provider';
	return $providers;
} );

add_action( 'vrb_export_completed', function ( $report_id, $format, $rows, $context ) {
	error_log( sprintf( 'Report %d exported: %d rows as %s (%s)', $report_id, $rows, $format, $context['destination'] ) );
}, 10, 4 );

add_filter( 'vrb_query_post_statuses', function ( $statuses ) {
	$statuses[] = 'future';
	return array_unique( $statuses );
} );

Testing your integration

  • Run your listener against real activity: open a report, export it, and check the action fired (the example’s wp wrs-example activity shows how).
  • Assert on hook arguments in your own tests with did_action() or a recording listener; argument shapes are documented above.
  • A WordPress Playground blueprint is the quickest disposable test site. WP Report Studio’s own SQLite parity harness (tests/playground/ in the source) shows the pattern.

Reporting gaps

If you need a hook that isn’t here, or an argument you can’t get, open a support request describing the integration. Lifecycle actions are Beta precisely so they can change in response.