-
Notifications
You must be signed in to change notification settings - Fork 0
Hooks and Filters
Presto Player and Presto Player Pro are built to be customized. Almost everything — what gets rendered, who can view a private video, how long a signed CDN link stays valid, what happens when someone submits their email — runs through a WordPress action or filter, so you can change it from your own theme or a small helper plugin without touching Presto Player's code.
If you're new to this: an action lets you run your own code at a specific moment (add_action). A filter lets you change a value before Presto Player uses it (add_filter, and you must return something). Every example below is safe to drop into a site-specific plugin or your theme's functions.php.
This page is a complete reference generated from the plugin source, grouped by what you're trying to do. Use Cmd/Ctrl+F to jump to a hook name.
- Video Rendering & Blocks
- Playback & View Tracking
- Frontend JS Player Events
- Private Videos & Access Control
- Licensing
- REST API
- File Storage & Media Library
- Data Models (Videos, Presets, Webhooks…)
- Settings, Branding & Dynamic Text
- Pro: Bunny CDN Storage & Streaming
- Pro: Video Transcription (Captions)
- Pro: Email Collection Forms
- Pro: Analytics
- Pro: Third-Party API Requests (Bunny, Mailchimp, etc.)
- More Examples
These fire while a video block is turned into the HTML that gets sent to the browser — the most useful group if you want to change what renders.
The big one. Fires just before Presto Player assembles the final config object it hands to the <presto-player> element — skin, branding, preset, tracks, overlays, everything.
add_filter( 'presto_player/block/default_attributes', function( $config, $attributes ) {
$config['class'] .= ' my-custom-wrapper';
return $config;
}, 10, 2 );Params: $config (array), $attributes (array, original block attributes) · inc/Support/Block.php:248
Return false to stop a specific video from rendering — the go-to hook for gating a video behind your own membership/paywall logic.
add_filter( 'presto_player_load_video', function( $load, $attributes, $content, $block_name ) {
if ( ! empty( $attributes['id'] ) && ! current_user_can( 'read_private_videos' ) ) {
return false;
}
return $load;
}, 10, 4 );Params: $load (bool), $attributes (array), $content (string), $name (string, block slug) · inc/Support/Block.php:527
Only fires when the hook above blocked a video. Return your own HTML instead of the default "unauthorized" message.
add_filter( 'presto_player_load_video_fallback', function( $fallback, $attributes, $content, $block ) {
return '<p>Please log in to view this video.</p>';
}, 10, 4 );Params: $fallback (bool|string), $attributes (array), $content (string), $block (Block instance) · inc/Support/Block.php:529
Fires earlier than default_attributes, right as the block's own saved attributes are read. Handy for overriding one attribute (e.g. force autoplay off for a specific provider) before the rest of the pipeline runs.
add_filter( 'presto_video_block_attributes_override', function( $attributes, $block ) {
if ( 'youtube' === $block->name ) {
$attributes['autoplay'] = false;
}
return $attributes;
}, 10, 2 );Params: $attributes (array), $this (Block instance) · inc/Support/Block.php:198
The final data handed to the template file, right before markup is generated — the last stop for tweaking what gets output.
Params: $data (array), $block (Block instance) · inc/Support/Block.php:537
Fires whenever a video or audio preset is resolved for a render. Use it to tweak preset values (skin, colors, watermark) at render time without editing the saved preset.
add_filter( 'presto_player/presto_player_presets/data', function( $preset, $type ) {
if ( 'video' === $type ) {
$preset->skin = 'dark';
}
return $preset;
}, 10, 2 );Params: $preset (Preset|AudioPreset object), $type ('video' or 'audio') · inc/Support/Block.php:351 (video) and :368 (audio)
The list of block names Presto Player recognizes as "a video block" (presto-player/self-hosted, presto-player/youtube, etc.). Add your own if you're building a custom provider block on top of Presto Player.
add_filter( 'presto_player_registered_block_types', function( $types ) {
$types[] = 'presto-player/my-custom-provider';
return $types;
} );Params: $block_types (array) · inc/Models/Block.php:20
Fires once per block while Presto Player scans a post's content looking for a video. Useful if you wrap Presto Player blocks in your own container blocks and need to unwrap them here.
Params: $block (array|null, parsed block) · inc/Models/Post.php:84
Fires literally inside the opening <presto-player ...> tag. The sanctioned way to add your own data-* attribute to the rendered player element.
add_action( 'presto_player/templates/player_tag', function( $data ) {
echo ' data-my-tracking-id="' . esc_attr( $data['id'] ) . '"';
} );Params: $data (array, same data driving the whole render) · templates/video.php:42
Control the Google-style VideoObject JSON-LD schema Presto Player prints for SEO. Return false from the first to disable it for a video (e.g. private content you don't want indexed); use the second to add/adjust properties like duration.
Params: $enabled (bool) / $data (array) · and · $schema (array) / $data (array) · inc/Support/Block.php:600 / :629
Fires once per block instantiation, passing a reference to that block's internal middleware() gating method. This is an advanced/low-level hook — most people want presto_player_load_video instead, which is simpler and fires with real attributes. · inc/Support/Block.php:119
Controls the no-JS iframe fallback markup Presto Player prints for YouTube/Vimeo embeds. Presto Player turns this on automatically when needed; override if you need the fallback in a context it doesn't detect. · inc/Services/Scripts.php:460
Fires every time the frontend reports playback progress. Great for triggering a "video completed" automation once someone crosses a percentage threshold.
add_action( 'presto_player_progress', function( $id, $percent, $visit_time ) {
if ( $percent >= 90 ) {
do_action( 'my_plugin_video_completed', get_current_user_id(), $id );
}
}, 10, 3 );Params: $id (int, video ID), $percent (int), $visit_time (int|false, seconds watched) · inc/Services/Player.php:86
Master switch for the lightweight daily view-counter used on the dashboard KPI widget. Return false to turn it off entirely.
add_filter( 'presto_player_daily_views_enabled', '__return_false' );Params: $enabled (bool, default true) · inc/Services/Scripts.php:182, inc/Services/Usage.php:725 and :747
Controls whether Presto Player enqueues its main frontend JS bundle on the current page. Force it true if a player gets injected in a way Presto Player's auto-detection misses.
add_filter( 'presto_player_load_js', function( $should_load ) {
return $should_load || is_page( 'custom-video-landing' );
} );Params: $should_load (bool) · inc/Services/Scripts.php:447
These are plain JavaScript, not PHP — they fire in the browser as someone actually watches a video, using WordPress's own wp.hooks JS library (the same one block editor extensions use). Every player on the page fires them, so this is the easiest way to react to playback from your own JS without touching PHP at all.
They all follow the pattern presto.player{Event}, and every callback receives the underlying plyr.io player instance.
wp.hooks.addAction( 'presto.playerReady', 'my-plugin-namespace', ( player ) => {
console.log( { player } );
} );
wp.hooks.addAction( 'presto.playerTimeUpdate', 'my-plugin-namespace', ( player ) => {
console.log( 'current time is ' + player.currentTime );
} );| Event | Fires when |
|---|---|
presto.playerReady |
The player instance is ready for API calls |
presto.playerPlay |
Playback resumes after being paused |
presto.playerPlaying |
Playback begins (first time, after pausing, or after replaying) |
presto.playerPause |
Playback is paused |
presto.playerTimeUpdate |
The current playback time changes |
presto.playerEnded |
Playback finishes (doesn't fire if autoplay looping) |
presto.playerSeeked |
A seek operation completes |
presto.playerEnterFullScreen |
The player enters fullscreen |
presto.playerExitFullScreen |
The player exits fullscreen |
presto.playerHidden |
The tab is closed or loses focus while a video is playing |
presto.playerVisible |
The tab regains focus after having been hidden |
Second argument to addAction is just your own namespace string (like a slug) — use something unique to your plugin/theme so WordPress doesn't collide it with someone else's. · packages/components/src/components/core/player/functions/actions.js
The main gate for private videos. Default behavior is "must be logged in" — override it to plug in your own membership/rules logic.
add_filter( 'presto-player-show-private-video', function( $can_view, $id ) {
return user_can( get_current_user_id(), 'view_premium_videos' );
}, 10, 2 );Params: $can_view (bool, default: is_user_logged_in()), $id (int, video/attachment ID) · inc/Models/CurrentUser.php:21
Mostly relevant if you're building on top of Presto Player rather than just using it — white-labeling, VIP/enterprise deployments, or custom renewal flows.
| Hook | Type | Default | What it's for |
|---|---|---|---|
presto_player_license_api_url |
filter | https://my.prestomade.com/index.php |
Point license checks at a different server (staging/mirror/on-prem) |
presto_player_license_domain |
filter | site's wpurl, scheme stripped |
The domain string sent to the licensing server — useful behind reverse proxies |
presto_player_license_renew_url |
filter | https://my.prestomade.com/my-account/ |
The "Renew Now" link in the expired-license notice |
presto_player_license_pricing_url |
filter | https://prestoplayer.com/pricing/ |
The "Buy Subscription" link in the no-key notice |
presto_player_license_is_vip |
filter | computed | Whether this counts as a WP VIP-provisioned (always-licensed) build |
presto_player_force_license_check |
filter | false |
Forces the real license check to run even under PRESTO_TESTSUITE
|
All in inc/Services/License/License.php except the last, in inc/Plugin.php:42.
Fires right before a REST-submitted video item is written to the database. Add/validate/strip fields on create or update requests through /wp-json/presto-player/v1/videos.
add_filter( 'presto_player_rest_prepared_database_item', function( $prepared ) {
$prepared['source_ip'] = $_SERVER['REMOTE_ADDR'] ?? '';
return $prepared;
} );Params: $prepared (array) · inc/Services/API/RestVideosController.php:408
Fires right before a video item is sent back in a REST response — add computed/custom fields for your own frontend to consume.
Params: $prepared (array) · inc/Services/API/RestVideosController.php:427
If you're extending the block editor or frontend JS and need extra data available on window, these are where Presto Player builds those payloads:
| Hook | JS global | File |
|---|---|---|
presto-settings-block-js-options |
window.prestoPlayer (frontend) |
Services/Scripts.php:160 |
presto_player_admin_script_options |
prestoPlayer (block editor) |
Services/Scripts.php:243 |
presto_player_admin_block_script_options |
prestoPlayerAdmin (block editor, incl. transcription endpoints) |
Services/Scripts.php:292 |
Fires after Presto Player's onboarding REST endpoint activates an allowlisted companion plugin.
Params: $plugin_path (string), $plugin_slug (string) · inc/Services/PluginInstaller.php:136
Fires after a user's progress through the in-dashboard "Learn" walkthrough is saved.
Params: $progress (array, chapter_id => step_id => bool) · inc/Services/Learn.php:134
Two related filters (same purpose, different call sites — one at construction, one at folder-creation time) controlling the name of the private-media upload folder. Default: presto-player-private.
Files: inc/Files.php:36 and :236
Presto Player hides externally-hosted video attachments from the Media Library grid by default. Return true to show them anyway.
Params: $show (bool, default false) · inc/Files.php:109
Presto Player redirects the old edit.php?post_type=pp_video_block admin screen to the new Media Hub dashboard. Return false to keep the classic list-table view.
Params: $should_redirect (bool, default true), $target (string, dashboard URL) · inc/Services/VideoPostType.php:115
Every Presto Player database table (videos, presets, audio presets, webhooks, email collection) shares the same base Model class, so these three hooks each expand into one concrete hook per table:
| Table |
_created action |
_updated action |
/data filter |
|---|---|---|---|
| Videos | presto_player_videos_created |
presto_player_videos_updated |
presto_player/presto_player_videos/data |
| Presets | presto_player_presets_created |
presto_player_presets_updated |
presto_player/presto_player_presets/data |
| Audio Presets | presto_player_audio_presets_created |
presto_player_audio_presets_updated |
presto_player/presto_player_audio_presets/data |
| Webhooks | presto_player_webhooks_created |
presto_player_webhooks_updated |
presto_player/presto_player_webhooks/data |
| Email Collection | presto_player_email_collection_created |
presto_player_email_collection_updated |
presto_player/presto_player_email_collection/data |
Note: the
/datafilter here is the low-level, fires-on-every-load-or-save filter (inc/Models/Model.php:652). It's a different, more general hook than the render-timepresto_player/presto_player_presets/datafilter documented under Video Rendering & Blocks — same name, different job (that one only fires for presets at render time; this one fires for every table, every time a row is read or written).
_created fires right after a new row is inserted:
add_action( 'presto_player_videos_created', function( $video ) {
error_log( 'New Presto Player video created: ' . $video->id );
} );_updated fires after any update (including trash/untrash, which use update() internally):
add_action( 'presto_player_videos_updated', function( $video ) {
// e.g. re-generate a cached embed snippet
} );Both actions receive the Model instance · inc/Models/Model.php:446 and :696
The default brand accent color used as an editor default and styling fallback (#00b3ff). Change it to reskin the default player color site-wide.
add_filter( 'presto_player_default_color', function() {
return '#ff5722';
} );Params: $color (string, hex) · inc/Models/Setting.php:96
The list of {token} → value replacements available in dynamic watermark/overlay text (e.g. {user.display_name}, {site.url}). Add your own custom tokens here.
add_filter( 'presto-player/dynamic-data', function( $values ) {
$values['{company.name}'] = get_bloginfo( 'name' );
return $values;
} );Params: $values (array) · inc/Support/DynamicData.php:23
presto_player_bunny_read_permissions / _connect_permissions / _upload_permissions / _sign_url_permissions (filters)
Capability gates for the Bunny CDN REST endpoints (browse/list files, connect an account, upload, or request a signed private URL). Default requires upload_files + edit_posts (read), building up from there. Use to open Bunny management up to a custom role.
add_filter( 'presto_player_bunny_upload_permissions', function( $allowed, $request ) {
return $allowed && current_user_can( 'edit_others_posts' );
}, 10, 2 );Params: $allowed (bool), $request (WP_REST_Request) · inc/Services/API/RestBunnyCDNController.php and RestBunnyStreamController.php
The whitelist of video file extensions/MIME types accepted for Bunny storage and stream uploads/listing/deletion. Add or remove supported formats.
add_filter( 'presto_player_allowed_mime_types', function( $mimes ) {
$mimes['ts'] = 'video/mp2t'; // allow MPEG transport stream uploads
unset( $mimes['wmv'] );
return $mimes;
} );Params: $mimes (array, extension pattern => MIME type) · RestBunnyCDNController.php and RestBunnyStreamController.php
How long a signed (token-authenticated) private Bunny CDN URL stays valid. Default: 2 hours.
add_filter( 'presto_player_bunny_token_expires', function( $seconds ) {
return 6 * HOUR_IN_SECONDS;
} );Params: $seconds (int, default 2 * HOUR_IN_SECONDS) · inc/Services/Bunny/URL.php:22
Default security settings applied when creating/updating a Bunny Stream video library (token auth, IP verification, MP4 fallback, etc.).
add_filter( 'presto_player_bunny_stream_default_library_config', function( $config, $type, $id ) {
if ( 'private' === $type ) {
$config['EnableTokenIPVerification'] = true;
}
return $config;
}, 10, 3 );Params: $config (array), $type ('public' or 'private'), $id (int, library ID) · inc/Controllers/BunnyVideoLibraryController.php:157
Who's allowed to trigger AI caption/transcription generation. Default requires upload_files + edit_posts.
add_filter( 'presto_player_transcribe_capability', function( $allowed, $request ) {
return current_user_can( 'manage_options' );
}, 10, 2 );Params: $allowed (bool), $request (WP_REST_Request) · RestBunnyTranscriptionController.php:233, RestBunnyWebhookController.php:204
Fires once per caption/subtitle track as it's prepared for the player. Rewrite caption URLs, rename labels, or add metadata.
add_filter( 'presto_player_bunny_caption_formatted', function( $formatted, $caption ) {
$formatted['label'] = strtoupper( $formatted['srcLang'] );
return $formatted;
}, 10, 2 );Params: $formatted (array: label, src, srcLang, version, source), $caption (raw Bunny caption metadata) · inc/Services/Bunny/BunnyTranscriptionService.php:251
The main integration point for leads — fires every time someone submits the email-collection form on a video, whether it's a new email or a repeat viewer.
add_action( 'presto_player/pro/forms/save', function( $data, $preset, $post, $created ) {
if ( $created ) {
wp_mail( 'sales@example.com', 'New video lead', $data['email'] . ' watched a video and left their email.' );
}
}, 10, 4 );Params: $data (array: email, firstname, lastname, video_id, preset_id), $preset (Preset/AudioPreset object), $post (WP_Post, the submission record), $created (bool — false if this email already existed) · inc/Services/EmailCollection/EmailCollectionAjaxHandler.php:71
Add your own validation rules to the email form (default only checks the email isn't empty).
add_filter( 'presto_player/pro/forms/validation', function( $errors ) {
if ( empty( $_POST['firstname'] ) ) {
$errors[] = 'Please enter your first name.';
}
return $errors;
} );Params: $errors (array of message strings) · inc/Services/EmailCollection/EmailCollectionAjaxHandler.php:152
Who can export collected email submissions via REST. Default: current_user_can('export').
Params: $allowed (bool) · inc/Services/API/RestEmailCollectionController.php:42
Return true to silently skip recording a video view — useful for excluding internal/staff IPs or test accounts from analytics.
add_filter( 'presto_player_analytics_block', function( $blocked, $ip, $user_id ) {
$internal_ips = array( '203.0.113.10' );
return in_array( $ip, $internal_ips, true ) ? true : $blocked;
}, 10, 3 );Params: $blocked (bool, default false), $ip (string), $id (int, user ID, 0 if logged out) · inc/Services/API/RestAnalyticsController.php:63
How far back analytics/visit data is kept before the daily cron purges it. Default: '90 days'.
add_filter( 'presto_player_analytics_time_period', function( $older_than ) {
return '30 days';
} );Params: $older_than (string, strtotime()-compatible) · inc/Services/Visits.php:81
Add a custom column to the "Top Users" analytics report.
add_filter( 'presto_top_user_stats', function( $row ) {
$row['stats'][] = array(
'id' => 'account_type',
'title' => 'Plan',
'data' => get_user_meta( $row['user']['id'], 'plan_type', true ),
);
return $row;
} );Params: $row (array with user and stats keys) · inc/Services/API/RestAnalyticsController.php:567
Every outgoing request to Bunny.net, MailerLite, ActiveCampaign, or MailChimp goes through the same three filters — each one expands into four concrete hook names, one per integration (bunny, mailerlite, active-campaign, mailchimp):
| Filter pattern | Fires | Params |
|---|---|---|
presto_player_{key}_request_args |
right before the HTTP request goes out |
$args (array, wp_remote_request-style), $endpoint (string) |
presto_player_{key}_request_endpoint |
same moment, for the URL path |
$endpoint (string), $args (array) |
presto_player_{key}_request_response |
after a successful response comes back |
$response (decoded JSON object), $args, $endpoint
|
So in practice you'd hook e.g. presto_player_bunny_request_args or presto_player_mailchimp_request_response.
add_filter( 'presto_player_bunny_request_args', function( $args, $endpoint ) {
$args['timeout'] = 30; // Bunny uploads can be large
return $args;
}, 10, 2 );
add_filter( 'presto_player_bunny_request_response', function( $response, $args, $endpoint ) {
error_log( 'Bunny API response for ' . $endpoint . ': ' . wp_json_encode( $response ) );
return $response;
}, 10, 3 );File: inc/Support/ApiRequest.php:37-71
Pro has its own copy of the same license-domain filter documented under Licensing above, for the LicensedProduct model used by add-ons. Same purpose, same signature. · inc/Models/LicensedProduct.php:52
Prefer copy-pasting a working file over reading a reference table? We keep a small collection of ready-to-use snippets in prestomade/api-examples:
| File | What it shows |
|---|---|
php-actions.php |
Hooking presto_player_progress and loading a video's data with the Video model |
email-capture.php |
Sending Pro email-collection submissions to your own list, plus custom validation |
js-actions.js |
Every frontend JS player event above, in one file |
Feel free to fork that repo and open a PR if you've built something worth sharing.
Didn't find the hook you were looking for, or think one's missing from this page? Open an issue on this repo, or ping us — we keep this page in sync with the plugin source.
Presto Player | Presto Player Pro | prestoplayer.com | PHP 7.3+ | WordPress 6.3+