Goals reference
Goals let you measure conversions in Burst Statistics. Each goal tracks a specific user action — a click, a page visit, or a WordPress hook execution — and records how often it occurs relative to your chosen conversion metric.
Overview
A goal is stored as a row in {prefix}_burst_goals. When a visitor completes a goal, a record is written to {prefix}_burst_goal_statistics linking that pageview to the goal. Goals are represented by the Burst\Frontend\Goals\Goal class and managed in bulk via Burst\Frontend\Goals\Goals. Burst supports two processing paths:
- Client-side — the browser tracking script detects the conversion (click or element view) and includes it in the tracking payload.
- Server-side — PHP detects the conversion (a WordPress hook fires or a page is visited) and writes the statistic directly.
Goals can be created from the goals admin UI, from integration templates, directly from the block editor, or from the Elementor editor. See Block editor goals and Elementor goals.
Pro - CreatorAvailable in the Creator tier
The Pro version allows unlimited goals. The free version permits up to three active goals at a time (Goal::LIMIT_FREE). Once three active goals exist, Goal::save() returns false for any new insert, and activating a fourth existing goal also fails. Learn more about goal conversion tracking
Goal types
Goal types are registered through the field definitions and can be extended with the burst_goal_types filter.
| Type | Tracking path | server_side | Description |
|---|---|---|---|
clicks | Client-side | false | Fires when a visitor clicks an element matching the configured CSS selector. |
views | Client-side | false | Fires when a visitor views an element matching the configured CSS selector. |
visits | Server-side | true | Fires when a visitor lands on the target page, matched by post ID or by URL. |
hook | Server-side | true | Fires when a specified WordPress action hook executes. |
server_side is derived automatically in Goal::get():
$this->server_side = $this->type === 'hook' || $this->type === 'visits';
Configuration fields
These fields map directly to the goal editor in the admin UI.
title
| Field | Value |
|---|---|
| Type | hidden (set programmatically) |
| Default | false (displays as "New goal" when empty) |
The human-readable name shown in the goals list.
status
| Field | Value |
|---|---|
| Type | hidden (toggled via the goal editor UI) |
| Default | false (inactive) |
| Accepted values | active, inactive |
Controls whether the goal is currently collecting conversions. Setting status to active resets date_start to the current time.
type
| Field | Value |
|---|---|
| Type | radio-buttons |
| Default | clicks |
| Accepted values | clicks, views, visits, hook |
The type of event to track. See Goal types for details on each option.
page_or_website
| Field | Value |
|---|---|
| Type | radio-buttons |
| Default | website |
| Accepted values | page, website |
| Shown for | clicks, views, hook goal types |
Determines whether the goal applies to the entire site or only to a specific page.
| Option | Description |
|---|---|
page | Track only when the conversion happens on a specific page |
website | Track the conversion anywhere on the site |
When website is selected, the stored url value is set to *.
specific_page
| Field | Value |
|---|---|
| Type | select-page |
| Default | false |
| Shown for | visits type, or when page_or_website is page |
The specific page to track. Accepts a relative URL. When page_or_website is website, this field is ignored and url is stored as *.
For page-scoped goals Burst resolves the matching post ID and stores it in page_id, so tracking can match on the post ID even after the page URL changes. See page_id.
The list of selectable pages comes from the posts/get endpoint. See Select page picker for what appears in it and how it is queried.
selector
| Field | Value |
|---|---|
| Type | selector |
| Default | '' (empty string) |
| Shown for | clicks and views goal types |
A CSS selector identifying the element to track. For example: #newsletter-form .submit-btn or .add-to-cart.
The value is sanitized with sanitize_text_field() before storage.
hook
| Field | Value |
|---|---|
| Type | hook |
| Default | '' (empty string) |
| Shown for | hook goal type only |
The WordPress action hook name to listen for. For example: woocommerce_payment_complete or my_plugin_form_submitted.
The value is sanitized with sanitize_text_field() before storage.
Hook-type goals rely on the burst_uid cookie being set by a prior client-side pageview. If the hook fires before the visitor has been tracked (for example, during a server-side-only request), the conversion will not be recorded.
conversion_metric
| Field | Value |
|---|---|
| Type | radio-buttons |
| Default | visitors |
| Accepted values | visitors, sessions, pageviews |
Determines the denominator used when calculating the conversion rate.
| Option | Description |
|---|---|
visitors | Conversions ÷ unique visitors |
sessions | Conversions ÷ total sessions |
pageviews | Conversions ÷ total pageviews |
Select page picker
The select-page field is populated by the burst/v1/posts/get endpoint, which returns the pages a page-scoped or visits goal can target.
Rows come from the {prefix}_burst_page_urls dictionary: one canonical row per post ID, joined to the published post in {$wpdb->posts} for its title. Any public post type with a canonical dictionary row is listed, so WooCommerce products and other plugin post types appear alongside posts and pages, each with the URL the tracker actually stores for it.
The endpoint never calls get_permalink() on the dictionary path. Burst's REST optimizer leaves plugin post types unregistered on burst/v1 requests, so get_permalink() would return the wrong URL for exactly those post types the dictionary makes available.
Until the dictionary has been seeded, the endpoint falls back to published posts and pages from {$wpdb->posts}. Only those two core post types are listed on that path, and because core post types stay registered on burst/v1 requests, get_permalink() resolves correctly there.
Search and result limits
| Parameter | Type | Default | Description |
|---|---|---|---|
nonce | string | — | Required. Validated against the burst_nonce action. |
search | string | '' | Sanitized with sanitize_text_field(). Matched against the page URL and the post title on the dictionary path; against the post title only on the fallback path. |
A search term of more than three characters raises the result cap from 100 to 1000 rows. Results are ordered by post title, ascending.
Pageviews
Each returned row carries a pageviews total. Burst resolves these for the listed pages only, through an indexed IN lookup on statistics.page_id executed with Statistics_Query — never a GROUP BY over the whole statistics table. Running it through the query executor keeps the timeout cap, result cache and single-flight protection in place.
Return value
On success the endpoint responds 200 with:
Show code
[
'request_success' => true,
'posts' => [
[
'page_url' => '/shop/vintage-camera/', // path as stored by the tracker
'page_id' => 42,
'post_title' => 'Vintage camera',
'pageviews' => 128,
],
// …
],
'max_post_count' => 100,
]
On failure it returns a WP_Error: rest_forbidden (403) when the user lacks the view_burst_statistics capability, or rest_invalid_nonce (400) when the nonce is missing or expired.
Troubleshooting
- A custom post type does not appear in the picker. The dictionary only holds post types that have recorded traffic. Visit the post once so the tracker writes its canonical row, then reload the goal editor.
- URLs in the picker differ from
get_permalink()output. That is intended: the picker shows the URL the tracker stores, which is whatvisitsgoals match against.
Goal object
The Burst\Frontend\Goals\Goal class represents a single goal. Properties are read/write via magic __get / __set.
| Property | Type | Default | Description |
|---|---|---|---|
id | int | 0 | Database primary key (ID column). |
title | string | '' | Human-readable goal name. Defaults to "New goal" when empty. |
type | string | 'clicks' | Goal type slug. See Goal types. |
status | string | 'inactive' | 'active' or 'inactive'. |
server_side | bool | false | Set automatically to true when type is 'hook' or 'visits'. Derived at load time — not stored as a separate column. |
url | string | '*' | Raw URL stored in the database. '*' means site-wide. |
page_or_website | string | 'website' | Derived from url. 'website' when url === '*'; 'page' otherwise. |
specific_page | string | '' | Relative URL used when page_or_website === 'page'. Derived from url. |
conversion_metric | string | 'visitors' | How conversions are counted. |
selector | string | '' | CSS selector for clicks and views goal types. |
hook | string | '' | WordPress action name for hook goal type. |
block_goal | int | 0 | Set to 1 when the goal was created from the block editor or the Elementor editor. Stored in the block_goal column. |
page_id | ?int | null | Post ID of the target page for page-scoped goals. Resolved from url on save. null for site-wide goals. |
is_draft | bool | false | true when page_id points to a post that is not published. Derived at load time — not stored as a separate column. |
date_start | int | — | Unix timestamp when the goal became active. Reset each time status changes to 'active', or on first creation. |
date_end | int | 0 | Unix timestamp when tracking ended. 0 means still running. |
date_created | int | 0 | Unix timestamp of first insertion. |
Deprecated properties
The following properties are deprecated and may be removed in a future release. Do not use them in new code.
| Property | Type | Replacement |
|---|---|---|
attribute | ?string | Use selector instead |
attribute_value | ?string | Use selector instead |
Tracking lifecycle
Client-side goals (clicks, views)
- The visitor interacts with an element matching the goal's
selector. - The frontend goals script appends the goal ID to the
completed_goalsarray in the tracking payload. - The tracking endpoint receives the payload and records the conversion in
{prefix}_burst_goal_statistics.
The tracking endpoint sanitizes the reported goal IDs with Tracking::sanitize_completed_goal_ids(): every value is cast to a positive integer, zero and duplicate IDs are dropped, and the total is capped at 50 IDs per hit. The endpoint is unauthenticated, so the cap bounds the work a single request can trigger — each reported ID becomes a row in one INSERT on {prefix}_burst_goal_statistics.
Server-side goals — hook type
- During the WordPress
initaction,Goals_Tracker::add_dynamic_hooks()reads every activehook-type goal and registers a WordPress action for each goal'shookproperty. - When that action fires,
Goals_Tracker::handle_hook( $hook_name )runs:- Reads the
burst_uidcookie to identify the visitor. - If no
burst_uidis present, the conversion is not recorded. - Retrieves the visitor's most recent statistic row via
get_last_user_statistic(). - If the goal is scoped to a specific page (
page_or_website === 'page'), checks thatspecific_pageappears in the storedpage_url + parametersstring. - Calls
create_goal_statistic()to write the conversion.
- Reads the
Show code
// Trigger a custom hook-type goal.
// 1. In Burst, create a goal with type = 'hook' and hook = 'my_plugin_form_submitted'.
// 2. Fire the action from your plugin:
do_action( 'my_plugin_form_submitted' );
// Goals_Tracker picks up the action and records the conversion
// for the currently identified visitor.
Server-side goals — visits type
Burst records a conversion when the tracked pageview matches the goal's target page. Matching is by post ID first: the tracking payload includes the page_id of the current page, and a visits goal stores a page_id for its target page — when both are present and equal, the conversion is recorded. When no page_id is available, Burst falls back to comparing the normalized page URL against the goal's stored url.
goal_is_completed() and get_completed_goals() receive the tracked page's post ID as an additional $page_id argument, which runs on both the REST tracking endpoint and the SHORTINIT beacon (where is_singular() / get_queried_object_id() are unavailable).
Goal conversion queries
Internally, Goal_Statistics::get_goal_completed_count_sql() builds the SQL used to count goal completions. The query JOINs {prefix}_burst_sessions so that session-level attributes such as device_id are read from the sessions table rather than the statistics table.
device_id (and the related browser_id, platform_id, browser_version_id, first_time_visit, and bounce columns) live on {prefix}_burst_sessions, not {prefix}_burst_statistics. Custom SQL that queries goal conversions must JOIN {prefix}_burst_sessions and reference sessions.device_id rather than statistics.device_id.
get_goal_completed_count_sql() accepts a single goal ID (int) or a list of goal IDs (int[]). When passed a single ID, the count expression follows that goal's conversion_metric and the query constrains on goals.goal_id = %d. When passed an array of IDs, the conversions are counted as distinct visitors (COUNT(DISTINCT(statistics.uid_id))) and the query constrains on goals.goal_id IN (…).
The generated SQL for a single goal takes the following form:
Show code
// Simplified representation of the query produced by get_goal_completed_count_sql()
// for a single goal id.
global $wpdb;
$sql = $wpdb->prepare(
"SELECT COUNT(DISTINCT statistics.uid_id) AS value
FROM {$wpdb->prefix}burst_goal_statistics AS goals
INNER JOIN {$wpdb->prefix}burst_statistics AS statistics
ON statistics.ID = goals.statistic_id
INNER JOIN {$wpdb->prefix}burst_sessions AS sessions
ON statistics.session_id = sessions.ID
WHERE goals.goal_id = %d
AND statistics.time > %s",
$goal_id,
$date_start
);
Visitor identity lives in the integer statistics.uid_id column (a dictionary id into {prefix}_burst_uids); count distinct visitors on statistics.uid_id, not on a raw uid string.
Sessions without a pageview
{prefix}_burst_sessions carries a has_pageview column, set to 1 as soon as a session records a hit whose page_type is not 404. A session made up of 404 hits only — typically a bot scan — keeps has_pageview = 0.
Session-level goal queries filter on sessions.has_pageview = 1, so those sessions never contribute to goal metrics. This mirrors the hit-level rule that excludes 404 hits from every pageview query, keeping session-grain and hit-grain numbers aligned. Custom SQL that aggregates goal or device data over {prefix}_burst_sessions should apply the same condition:
Show code
// Pageviews per device — sessions JOIN required since device_id lives on burst_sessions.
// has_pageview = 1 keeps 404-only bot sessions out of the result.
$sql = $wpdb->prepare(
"SELECT COUNT(statistics.uid_id) AS value, sessions.device_id
FROM {$wpdb->prefix}burst_statistics AS statistics
INNER JOIN {$wpdb->prefix}burst_sessions AS sessions
ON statistics.session_id = sessions.ID
WHERE statistics.time > %s
AND sessions.has_pageview = 1
GROUP BY sessions.device_id
ORDER BY value DESC
LIMIT 4",
$date_start
);
The device-breakdown query used to determine the best-performing device for a goal applies the same sessions.has_pageview = 1 condition.
Aggregated goal statistics
Goal_Statistics::get_goals_data() and Goal_Statistics::get_live_goals_count() accept the special goal_id value 'all', which aggregates conversions across every active goal instead of reporting on a single goal.
When goal_id is 'all':
- The set of active goal IDs is resolved from
{prefix}_burst_goals WHERE status = 'active'. If no active goals exist, an empty/zeroed result is returned. - Conversions are counted as distinct visitors (
COUNT(DISTINCT(statistics.uid_id))) across all active goals, regardless of each goal's individualconversion_metric. date_startis the earliestdate_createdof the active goals, and no per-goal URL constraint is applied.- The returned
statusis'active'andgoalIdis the string'all'.
Show code
// Aggregated counts across all active goals.
$all = burst_loader()->admin->statistics->goal_statistics->get_goals_data( [
'goal_id' => 'all',
] );
// Aggregated live count (conversions since midnight) across all active goals.
$live = burst_loader()->admin->statistics->goal_statistics->get_live_goals_count( [
'goal_id' => 'all',
] );
The goal_id argument is sanitized in App::sanitize_arg(): the literal string 'all' is preserved, and any other value is coerced to a positive integer with absint().
Live goal counts and the site timezone
get_live_goals_count() counts conversions since midnight in the site's timezone, as configured under Settings → General → Timezone in WordPress. The boundary is resolved with wp_timezone():
// Midnight in the site's timezone, not UTC midnight.
$today = ( new \DateTimeImmutable( 'today', wp_timezone() ) )->getTimestamp();
WordPress pins the PHP process to UTC, so a plain strtotime( 'today midnight' ) would start the day at UTC midnight and drop the early-morning conversions of sites east of Greenwich. Custom code that reproduces the live count must resolve midnight through wp_timezone() the same way.
get_goals_data return shape
get_goals_data() returns the following structure. The goalId key is an int for a single goal and the string 'all' for the aggregated view.
Show code
[
'today' => [ 'value' => int, 'tooltip' => string ],
'total' => [ 'value' => int, 'tooltip' => string ],
'topPerformer' => [ 'title' => string, 'value' => int ],
'conversionMetric' => [ 'title' => string, 'value' => int, 'tooltip' => string, 'icon' => string ],
'conversionPercentage' => [ 'title' => string, 'value' => int, 'tooltip' => string ],
'bestDevice' => [ 'title' => string, 'value' => int, 'icon' => mixed ],
'dateCreated' => int,
'dateStart' => int,
'dateEnd' => int,
'status' => string,
'goalId' => int|string,
]
Database tables
| Table | Purpose |
|---|---|
{prefix}_burst_goals | One row per goal; stores all goal configuration, including the block_goal and page_id columns. |
{prefix}_burst_goal_statistics | Many-to-many join between statistic rows and goal IDs. All rows for a goal are deleted when the goal is deleted. |
{prefix}_burst_page_urls | Page URL dictionary. Supplies the canonical URL and post ID pairs for the select page picker. |
Tables are created via the burst_install_tables action. See burst_install_tables.
The block_goal (tinyint, default 0) and page_id (int, nullable) columns are added by database upgrades on existing installs. Goal::save() only writes these columns when they are already present in the schema, so goals still save on installs that have not yet run the upgrade.
Block editor goals
Burst adds a "Track clicks with a Burst Goal" inspector panel to supported blocks in the block editor (available to users with the manage_burst_statistics capability). Enabling it on a block stores two block attributes — burstGoalActive and burstGoalUid — and creates or updates a goal through the goals/upsert_for_block endpoint.
On the frontend, the render_block filter injects a data-burst-goal="<uid>" attribute into the block's first HTML tag, and the goal's stored selector is [data-burst-goal="<uid>"]. Clicks on that element are then tracked as a clicks-type conversion. The uid is always the source of truth for the selector — the client cannot override it.
Block goals are marked with block_goal = 1 and Burst keeps them in sync with post content:
- On
save_post, goals whosepage_idmatches the post have theirurlrewritten to the post's current path. Any block goal whoseuidno longer appears in the post content is retired throughGoals::retire_goal_if_orphaned()(see retire_goal_if_orphaned). Elementor-managed posts are skipped here — they are synchronised from_elementor_datainstead (see Elementor goals). - A daily cleanup (
cleanup_orphaned_block_goals) removes or deactivates block goals whoseuidis absent from all non-trash, non-revision post content and from Elementor's_elementor_datapost meta, skipping goals created within the last 3 hours to avoid races while editing.
Clickable blocks
Goals::get_clickable_blocks() returns the list of Gutenberg block names that support click goal tracking in the inspector panel. The list is also passed to the frontend tracking script as clickable_blocks.
$blocks = \Burst\Frontend\Goals\Goals::get_clickable_blocks();
// [ 'core/button', 'core/image', 'core/navigation-link' ]
Extend or restrict the list with the burst_clickable_blocks filter.
Block attribute registration
Burst registers the burstGoal* block attributes in the PHP block registry through the register_block_type_args filter, so server-side rendered blocks (for example wpforms/form-selector and WooCommerce blocks) and the /wp/v2/block-renderer endpoint validate them instead of throwing Invalid parameter(s): attributes errors.
The registered schema properties are:
| Attribute | Type |
|---|---|
burstGoalUid | string |
burstGoalActive | boolean |
burstGoalId | number |
burstGoalType | string |
No defaults are declared: only the schema properties matter for validation, and a default would inject these keys into every dynamic block on every render. The block editor script supplies its own client-side defaults. The core/freeform block is skipped.
Goal::get_by_uid
Look up a block goal by its uid. Returns a Goal instance for the row whose selector equals [data-burst-goal="<uid>"], or null when none matches.
Show code
$goal = \Burst\Frontend\Goals\Goal::get_by_uid( 'abc123' );
if ( $goal !== null ) {
echo $goal->id;
}
Goals::retire_goal_if_orphaned
Retire a block or element goal whose uid is no longer present in any content: the goal is deleted when it has no statistics, or set to inactive when it does so existing statistics are preserved. This is the single source of truth shared by the block editor save_post cleanup, the daily cleanup_orphaned_block_goals sweep, and the Elementor sync.
// Delete or deactivate an orphaned block/element goal.
$retired = \Burst\Frontend\Goals\Goals::retire_goal_if_orphaned( 42 );
// Returns true when the goal was deleted or deactivated, false otherwise.
Elementor goals
Burst adds a "Burst Goal" controls section to the Advanced tab of Elementor widgets, sections, containers and columns (available to users with the manage_burst_statistics capability). Elementor goals are stored the same way as block editor goals — marked with block_goal = 1, with a selector of [data-burst-goal="<uid>"] — and share the same retirement logic through Goals::retire_goal_if_orphaned().
On the frontend, the elementor/frontend/before_render action injects a data-burst-goal="<uid>" attribute into the element wrapper, and clicks on (or views of) that element are tracked accordingly. Goals are synchronised to the database when an Elementor document is saved, via both the elementor/editor/after_save action and _elementor_data post-meta updates, so imports and direct meta writes stay in sync.
Controls
| Control | Description |
|---|---|
| Track with a Burst Goal | Toggle that enables goal tracking for the element |
| Goal trigger type | clicks or views for widgets; views only for sections, containers and columns |
| Goal title | Human-readable name; auto-generated from the element type and trigger when left empty |
| Conversion metric | visitors, sessions or pageviews |
| Page scope | page (current page) or website (all pages) |
When a goal is copied to another post (for example via Duplicate Post, WPML or an importer), the sync generates a fresh uid for the new post's goal rather than reassigning the original.
Clickable widgets
Elementor::get_clickable_widgets() returns the Elementor widget types considered clickable. When an element is not in this list, its goal is forced to the views trigger type.
$widgets = \Burst\Integrations\Plugins\Elementor\Elementor::get_clickable_widgets();
// [ 'button', 'icon', 'icon-box', 'image', 'image-box', 'call-to-action', ... ]
Extend or restrict the list with the burst_elementor_clickable_widgets filter.
Predefined goals
Pro - CreatorAvailable in the Creator tier
Integrations can register predefined goal templates. These are surfaced in the goals UI when the corresponding third-party plugin is active. Learn more about goal conversion tracking
Goals::get_predefined_goals() collects goal templates from each registered integration and returns them as a flat list. Templates are keyed by their id while collecting, so if more than one active integration declares the same goal id, the last one wins and the list contains no duplicate IDs.
Show code
/**
* @param bool $skip_active_check Pass true to include goals from inactive integration plugins.
* @return array<int, array{
* id: string,
* type: string,
* description: string,
* status: string,
* server_side: bool,
* url: string,
* hook: string
* }>
*/
$predefined = burst_loader()->frontend->goals->get_predefined_goals();
By default only goals from active integration plugins are returned.
A predefined goal template can be saved with Goal::add_predefined( string $id ):
$goal = new \Burst\Frontend\Goals\Goal();
$new_id = $goal->add_predefined( 'woocommerce-purchase' );
// Returns the new goal ID on success, or 0 on failure.
add_predefined() sets status = 'active', conversion_metric = 'visitors', and url = '*' as defaults before calling save().
Available predefined goals by integration
| Integration | Goal ID | Type | Trigger |
|---|---|---|---|
| Elementor | elementor_pro_forms_form_submitted | hook | elementor_pro/forms/form_submitted |
| Elementor | submit_button_click | clicks | .elementor-field-type-submit .elementor-button |
| WooCommerce | woocommerce_add_to_cart | hook | woocommerce_add_to_cart |
| WooCommerce | woocommerce_checkout_order_created | hook | woocommerce_checkout_order_created |
| WooCommerce | woocommerce_payment_complete | hook | woocommerce_payment_complete |
| WooCommerce | woocommerce_add_to_cart_click | clicks | .add_to_cart_button |
| WooCommerce | woocommerce_click_checkout_button | clicks | .wc-block-cart__submit-button |
| Easy Digital Downloads | edd_complete_purchase | hook | edd_complete_purchase |
| Easy Digital Downloads | edd_add_to_cart | clicks | .edd-add-to-cart |
| Easy Digital Downloads | edd_go_to_checkout | clicks | .edd_go_to_checkout |
| Easy Digital Downloads | edd_click_purchase | clicks | #edd-purchase-button |
| Admin and Site Enhancements (ASE) | ase_contact_form_submit_click | clicks | .asenha-cf-submit-button |
| Admin and Site Enhancements (ASE) Pro | formbuilder_after_email | hook | formbuilder_after_email |
| Admin and Site Enhancements (ASE) Pro | ase_pro_form_submit_click | clicks | .fb-submit-button |
Querying goals
Show code
$goals = burst_loader()->frontend->goals->get_goals( [
'status' => 'active', // 'active', 'inactive', or 'all' (default)
'limit' => 10, // default: 9999
'offset' => 0, // default: 0
'orderby' => 'ID', // any column in burst_goals; default: 'ID'
'order' => 'ASC', // 'ASC' or 'DESC'; default: 'ASC'
'date_start' => strtotime( '-30 days' ), // Unix timestamp; -1 = no filter (default)
'date_end' => time(), // Unix timestamp; default: time()
] );
foreach ( $goals as $goal ) {
echo $goal->id . ': ' . $goal->title . ' (' . $goal->type . ')';
}
date_start and date_end filter on date_created. The orderby value is validated against the actual columns of burst_goals; unknown values fall back to 'ID'.
Saving and deleting goals
Creating a new goal
Show code
$goal = new \Burst\Frontend\Goals\Goal();
$goal->title = 'Newsletter signup';
$goal->type = 'clicks';
$goal->selector = '#newsletter-form .submit-btn';
$goal->status = 'active';
$success = $goal->save();
echo $goal->id; // populated after a successful insert
Updating an existing goal
$goal = new \Burst\Frontend\Goals\Goal( 42 );
$goal->status = 'inactive';
$goal->save();
Deleting a goal
$goal = new \Burst\Frontend\Goals\Goal( 42 );
$goal->delete(); // removes the goal row AND all burst_goal_statistics rows for this goal
Save behaviour
title,selector, andhookare sanitized withsanitize_text_field().urlis sanitized as a relative URL. Whenpage_or_website === 'website'the stored value is forced to'*'.- For
page-scoped goals Burst resolvespage_idfrom the URL (or resolves the URL from a suppliedpage_idfor published posts) sovisitsmatching can use the post ID. block_goalandpage_idare written only when those columns exist in the schema.server_sideis derived from the goal type definition — not settable by the caller.date_startis set totime()on first creation or wheneverstatuschanges to'active'.- Returns
trueon success,falseon failure (including exceeding the free-tier limit ofGoal::LIMIT_FREE(3) active goals). Activating an existing goal when already at the limit also fails.
Goal::can_add_goal() decides whether another active goal is allowed: it returns true when the burst_license_is_valid filter reports a valid Pro license, otherwise true only while fewer than Goal::LIMIT_FREE (3) active goals exist.
REST API endpoints
All goals endpoints are under the burst/v1 namespace and require a valid nonce.
| Endpoint | Method | Permission | Description |
|---|---|---|---|
burst/v1/goals/get | GET | view | Returns all configured goals, the list of predefined goals, and the current goal_limit and active_goals_count. |
burst/v1/goals/add | POST | manage | Creates a new goal. |
burst/v1/goals/set | POST | manage | Updates an existing goal. |
burst/v1/goals/upsert_for_block | POST | manage | Creates or updates a goal from the block editor, keyed by the block uid. |
burst/v1/goals/delete | POST | manage | Deletes a goal and its associated statistics. |
burst/v1/goals/add_predefined | POST | manage | Adds a predefined goal from an integration. |
burst/v1/posts/get | GET | view | Returns the pages the goal "select page" picker can choose from. See Select page picker. |
burst/v1/data/goals | GET | view | Returns goal conversion statistics for the selected date range. |
burst/v1/data/live-goals | GET | view | Returns live goal conversion counts (since midnight in the site's timezone). |
The data/goals and data/live-goals endpoints accept a goal_id argument. Pass a numeric goal ID to report on a single goal, or the literal string all to aggregate conversions across every active goal. See Aggregated goal statistics.
goals/upsert_for_block resolves the target goal by the block uid (via Goal::get_by_uid()), falling back to id / goal_id. A uid is required for any create or update: when the goal cannot be resolved and no uid is supplied, the endpoint responds 400 with Goal UID is required.. The only exception is deleting an already-existing goal resolved by its id. The endpoint enforces the active-goal limit before inserting, builds the selector from the uid, and marks new goals with block_goal = 1. Passing status: delete removes the goal.
Permission levels:
view— requiresview_burst_statisticscapabilitymanage— requiresmanage_burst_statisticscapability (typically administrators)
Hooks and filters
burst_goal_types
Filter the available goal type definitions. Controls which types appear in the goal editor and are accepted by Goal::save().
Parameters:
| Parameter | Type | Description |
|---|---|---|
$types | array | Associative array of goal type definitions keyed by type slug. Each entry may include label, description, icon, and server_side. |
Example:
Show code
add_filter( 'burst_goal_types', function( array $types ): array {
$types['video_play'] = [
'label' => __( 'Video play', 'my-plugin' ),
'description' => __( 'Fires when a video starts playing.', 'my-plugin' ),
'server_side' => false,
];
return $types;
} );
burst_allowed_goal_types
Filters the normalized list of goal type slugs accepted by Burst's sanitization layer.
Use this alongside burst_goal_types when you introduce a custom type and want it to survive request validation.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$goal_types | string[] | Allowed goal type slugs. |
Example:
Show code
add_filter( 'burst_allowed_goal_types', function( array $goal_types ): array {
$goal_types[] = 'video_play';
return array_values( array_unique( $goal_types ) );
} );
burst_clickable_blocks
Filters the list of Gutenberg block names that support click goal tracking in the block editor inspector panel. See Clickable blocks.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$clickable_blocks | string[] | Block type names considered clickable. Defaults to core/button, core/image and core/navigation-link. |
Example:
Show code
add_filter( 'burst_clickable_blocks', function( array $clickable_blocks ): array {
$clickable_blocks[] = 'core/buttons';
return array_values( array_unique( $clickable_blocks ) );
} );
burst_elementor_clickable_widgets
Filters the list of Elementor widget type names that support click goal tracking in the Elementor editor. Widgets not in this list are forced to the views trigger type. See Clickable widgets.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$clickable_widgets | string[] | Elementor widget type names considered clickable. Defaults include button, icon, icon-box, image, image-box, call-to-action, animated-headline, price-table, form, nav-menu and search-form. |
Example:
Show code
add_filter( 'burst_elementor_clickable_widgets', function( array $clickable_widgets ): array {
$clickable_widgets[] = 'my-custom-cta';
return array_values( array_unique( $clickable_widgets ) );
} );
burst_before_save_goals
Fires immediately before a goal is written to the database (both insert and update).
Parameters:
| Parameter | Type | Description |
|---|---|---|
$goal | \Burst\Frontend\Goals\Goal | The Goal object about to be saved. Public properties can be read or mutated. |
Example:
Show code
add_action( 'burst_before_save_goals', function( \Burst\Frontend\Goals\Goal $goal ): void {
if ( $goal->type === 'hook' && empty( $goal->hook ) ) {
$goal->hook = 'woocommerce_thankyou';
}
} );
burst_after_save_goals
Fires after a goal has been successfully saved and the object refreshed from the database. Does not fire when save() returns false.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$goal | \Burst\Frontend\Goals\Goal | The saved Goal object with current database values, including the new id for inserts. |
Example:
Show code
add_action( 'burst_after_save_goals', function( \Burst\Frontend\Goals\Goal $goal ): void {
if ( $goal->status === 'active' ) {
my_crm_sync_goal( $goal->id, $goal->title );
}
} );
burst_install_tables
Action fired by the Burst installer to trigger database table creation. Goals hooks into this at priority 10 to create {prefix}_burst_goals.
Parameters: none.
Example:
// Force table creation, e.g. after a manual reset.
do_action( 'burst_install_tables' );
burst_goal_fields
Filters the goal-editor field schema used by the admin app.
Use this to alter the fields shown for goal creation/editing, including adding custom controls or removing unsupported ones.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$fields | array | Goal field definitions loaded from goal-fields.php. |
burst_after_updated_goals
Fires after goal collections are updated through the REST API, including add, update, and delete flows handled by the admin app.
This is broader than burst_after_save_goals, which fires per-goal on model save.
Parameters: none.
Example:
add_action( 'burst_after_updated_goals', function(): void {
my_plugin_refresh_goal_dependent_cache();
} );
burst_license_is_valid
Filters whether a valid Pro license is active. Goal::can_add_goal() uses this to decide whether to allow unlimited goals; when no filter returns true, the free-tier limit of Goal::LIMIT_FREE (3) active goals applies.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$is_valid | bool | Whether a valid license is active. Defaults to false. |
Example:
if ( burst_license_is_valid() ) {
// Pro features are available.
}