Skip to Content


Get video metadata from Youtube or Vimeo.

PHP October 23, 2020


nebula()->video_meta($provider, $id)


(Required) (String) Which video service to check
Default: None

(Required) (String) The ID of the video to get metadata for
Default: None

Request or provide clarification »


Trackable Youtube video iframe

<?php $youtube_data = nebula()->video_meta('youtube', 'fjh61K3hyY0'); ?>
<div class="embed-responsive embed-responsive-16by9">
    <iframe id="<?php echo $youtube_data['id']; ?>" class="youtube embed-responsive-item" width="560" height="315" title="<?php echo $youtube_data['title']; ?>" src="//<?php echo $youtube_data['id']; ?>?wmode=transparent&enablejsapi=1&rel=0" allow="autoplay"></iframe>

Trackable Vimeo video iframe

<?php $vimeo_data = nebula()->video_meta('vimeo', '208432684'); ?>
<div class="embed-responsive embed-responsive-16by9">
    <iframe id="<?php echo $vimeo_data['id']; ?>" class="vimeo embed-responsive-item" src="<?php echo $vimeo_data['id']; ?>" width="560" height="315"></iframe>



See nebulaYoutubeTracking() for available Nebula data for Youtube videos.

Note: the enablejsapi=1 query parameter is needed for tracking to work. Nebula adds it automatically, but it is significantly faster to include it manually.

Note: it is recommended to use a title attribute on the iframe to enhance analytics reports with a video title (rather than a video ID).

Note: iframes can be lazy loaded with either hard-coded HTML (nebula-lazy class on noscript tag) or using the lazy_load() function (or one of its aliases).

Regular Youtube Video

For Syracuse (1:31) by Pinckney Hugo Group
It’s amazing to see all the creative, inspiring ways people are coming together right now. This is for our hometown.

Lazy-Loaded Youtube Video

This video was lazy-loaded.

PHG Overview Video (2:04) by Pinckney Hugo Group
Pinckney Hugo Group is a fully integrated ad agency. We take our work seriously, but not ourselves. We work ridiculous hours. We play well with others. We're good listeners. We pride ourselves on accomplishing what others can't. And we love what we do.

Youtube Video w/o JS API

Available Youtube Data

    "kind": "youtube#video",
    "etag": "8yZBpZB1iBDBYh1WK26Cd7Myd9o",
    "id": "KfJ3XcAiOJY",
    "snippet": {
        "publishedAt": "2021-08-31T13:48:17Z",
        "channelId": "UCrO9BZ4TaJ4taT91y9l1oCg",
        "title": "Hi Rochester.",
        "description": "We\u2019ve been doing work in the Rochester area since the start of PHG\u2014so this only made sense.\n\nTo learn more, visit: https:\/\/\/pinckney-hugo-group-expands-syracuse-headquarters-opens-rochester-office\/",
        "thumbnails": {
            "default": {
                "url": "https:\/\/\/vi\/KfJ3XcAiOJY\/default.jpg",
                "width": 120,
                "height": 90
            "medium": {
                "url": "https:\/\/\/vi\/KfJ3XcAiOJY\/mqdefault.jpg",
                "width": 320,
                "height": 180
            "high": {
                "url": "https:\/\/\/vi\/KfJ3XcAiOJY\/hqdefault.jpg",
                "width": 480,
                "height": 360
            "standard": {
                "url": "https:\/\/\/vi\/KfJ3XcAiOJY\/sddefault.jpg",
                "width": 640,
                "height": 480
            "maxres": {
                "url": "https:\/\/\/vi\/KfJ3XcAiOJY\/maxresdefault.jpg",
                "width": 1280,
                "height": 720
        "channelTitle": "Pinckney Hugo Group",
        "categoryId": "22",
        "liveBroadcastContent": "none",
        "localized": {
            "title": "Hi Rochester.",
            "description": "We\u2019ve been doing work in the Rochester area since the start of PHG\u2014so this only made sense.\n\nTo learn more, visit: https:\/\/\/pinckney-hugo-group-expands-syracuse-headquarters-opens-rochester-office\/"
        "defaultAudioLanguage": "en-US"
    "contentDetails": {
        "duration": "PT40S",
        "dimension": "2d",
        "definition": "hd",
        "caption": "false",
        "licensedContent": false,
        "contentRating": {},
        "projection": "rectangular"
    "statistics": {
        "viewCount": "588",
        "favoriteCount": "0"


See nebulaVimeoTracking() for available Nebula data for Vimeo videos.

Note: Nebula tracking prefers the Vimeo ID to be entered as a data attribute data-vimeo-id to match the Vimeo video ID (which will be a number). It also accepts this as the iframe ID and ultimately modifies the ID itself. If needing to select the iframe with CSS, you'll need to escape the ID selector.

Note: the query parameters api and player_id are no longer needed. In fact, they will prevent video tracking from working, so they must be removed!

Note: iframes can be lazy loaded with either hard-coded HTML (nebula-lazy class on noscript tag) or using the lazy_load() function (or one of its aliases).

OFF COURSE (12:46) by Ride Snowboards
Produced by: Gabe Langlois, Tanner McCarty, Jake Blauvelt
Directed and Edited by: Gabe Langlois
Principal Cinematography: Gabe Langlois, Paul Watt, Brandon Kelly
Aerial Cinematography: Gabe Langlois
Camera Assistant / Production Assistant: Christian Bertrand
Colour: David Tomiak /Silver Lining Post
Sound Mix: Keith White / Keith White Audio
Graphic Design: Javas Lehn
Featuring: jake Blauvelt, Shayne Pospisil, Jake Welch

A very special thanks to Tanner McCarty - Ride Snowboards, Andrew Goggins - Ten Barrel Brewing, Mike Last - Sigma Canada

A special thanks:

Leah Langlois
Kristen Blauvelt
The Langlois family
The Blauvelt family
Shin Campos
Stuart Andrews
Abby Andrew
Paul Watt
Christian Bertrand
Cholo Burns
Jenna Burns-Low
Adidas Snowboarding
Smith Optics
The North Face
Purl Wax
Salt and Stone
Rube and Sam Goldburg
Brandon Kelly
Dave Seaton
Lorne Lapham
Cole Brash
Shayne Pospisil
Jake Welch
Leanne Pelosi
Robin Van Gyn
Austin Sweetin
Skye Sheeley
TL City Locals
The Mighty Selkirks

This video was lazy-loaded.

California Inspires Me: Reggie Watts (3:45) by Drew Tyndell
Animation by Drew Tyndell
Music by Shannon Ferguson
Sound Production by Mooj Zadie
Special Thanks to Kevin Ferguson

California Inspires Me is a collaboration between Google Play and California Sunday Magazine.

Available Vimeo Data

    "id": 132454664,
    "title": "California Inspires Me: Reggie Watts",
    "description": "Animation by Drew Tyndell<br \/>\r\nMusic by Shannon Ferguson<br \/>\r\nSound Production by Mooj Zadie<br \/>\r\nSpecial Thanks to Kevin Ferguson<br \/>\r\n<br \/>\r\nCalifornia Inspires Me is a collaboration between Google Play and California Sunday Magazine.",
    "url": "https:\/\/\/132454664",
    "upload_date": "2015-07-02 12:46:22",
    "thumbnail_small": "http:\/\/\/video\/525124557-b80ce9983768edc012c970c11750c09a2bd9b629c28180c4569eaa2b94558d9e-d_100x75",
    "thumbnail_medium": "http:\/\/\/video\/525124557-b80ce9983768edc012c970c11750c09a2bd9b629c28180c4569eaa2b94558d9e-d_200x150",
    "thumbnail_large": "http:\/\/\/video\/525124557-b80ce9983768edc012c970c11750c09a2bd9b629c28180c4569eaa2b94558d9e-d_640",
    "user_id": 2624480,
    "user_name": "Drew Tyndell",
    "user_url": "https:\/\/\/drewtyndell",
    "user_portrait_small": "http:\/\/\/portrait\/20098251_30x30?sig=81e88db8cd96eef4fee1c450aa8d7a56c3e4d414cc3d6d8c1dead98bad462d89&v=1",
    "user_portrait_medium": "http:\/\/\/portrait\/20098251_75x75?sig=81e88db8cd96eef4fee1c450aa8d7a56c3e4d414cc3d6d8c1dead98bad462d89&v=1",
    "user_portrait_large": "http:\/\/\/portrait\/20098251_100x100?sig=81e88db8cd96eef4fee1c450aa8d7a56c3e4d414cc3d6d8c1dead98bad462d89&v=1",
    "user_portrait_huge": "http:\/\/\/portrait\/20098251_300x300?sig=81e88db8cd96eef4fee1c450aa8d7a56c3e4d414cc3d6d8c1dead98bad462d89&v=1",
    "stats_number_of_likes": 3897,
    "stats_number_of_plays": 107755,
    "stats_number_of_comments": 41,
    "duration": 225,
    "width": 1920,
    "height": 1080,
    "tags": "Computer Team, Drew Tyndell, Kevin Ferguson, Mooj Zadie, Shannon Ferguson, Google Play, Reggie Watts, California Sunday Magazine",
    "embed_privacy": "anywhere"


video_meta() is not used with HTML5 videos, but for demo purposes, Google Analytics tracking, and MediaSession API it is shown here for reference. See nebulaHTML5VideoTracking() for available Nebula data for HTML5 videos.

iOS requires videos to be muted for autoplay to work. Nebula will not send events to Google Analytics for videos that have both autoplay and loop attributes.

This video was lazy-loaded.

Additional Notes

This function returns an array that includes data such as “raw”, “title”, “safetitle”, “description”, “thumbnail”, “author”, “date”, “url”, “duration” (which is an array of “time” and “seconds”).

This function includes two aliases: vimeo_meta() and youtube_meta().

Sidenote: Remember that Youtube and Vimeo have their own APIs for JavaScript integration. It is recommended to read through these as there are many intricate nuances in each.

Was this page helpful? Yes No

    A feedback message is required to submit this form.

    Please check that you have entered a valid email address.

    Enter your email address if you would like a response.

    Thank you for your feedback!

    Source File

    Located in /libs/Functions.php on line 1270.

    1 Hook

    Find these filters and actions in the source code below to hook into them. Use do_action() and add_filter() in your functions file or plugin.

    Need a new filter hook? Request one here.

    This function has no action hooks available. Request one?

            public function video_meta($provider, $id){
                $override = apply_filters('pre_video_meta', null, $provider, $id);
                if ( isset($override) ){return $override;}
                $timer_name = $this->timer('Video Meta (' . $id . ')', 'start', 'Video Meta');
                $video_metadata = array(
                    'origin' => $this->url_components('basedomain'),
                    'id' => $id,
                    'error' => false
                if ( !empty($provider) ){
                    $provider = strtolower($provider);
                } else {
                    $video_metadata['error'] = 'Video provider is required.';
                    $this->timer($timer_name, 'end');
                    return $video_metadata;
                //Get Transients
                $video_json = nebula()->transient('nebula_' . $provider . '_' . $id, function($data){
                    if ( $data['provider'] === 'youtube' ){
                        if ( !$this->get_option('google_server_api_key') && $this->is_staff() ){
                            trigger_error('No Google Youtube Iframe API key. Youtube videos may not be tracked!', E_USER_WARNING);
                            echo '<script>console.warn("No Google Youtube Iframe API key. Youtube videos may not be tracked!");</script>';
                            $video_metadata['error'] = 'No Google Youtube Iframe API key.';
                        $response = $this->remote_get('' . $data['id'] . '&part=snippet,contentDetails,statistics&key=' . $this->get_option('google_server_api_key'));
                        if ( is_wp_error($response) ){
                            trigger_error('Youtube video is unavailable.', E_USER_WARNING);
                            $video_metadata['error'] = 'Youtube video is unavailable.';
                            $this->timer($data['timer_name'], 'end');
                            return $video_metadata;
                        $video_json = $response['body'];
                    } elseif ( $data['provider'] === 'vimeo' ){
                        $response = $this->remote_get('' . $data['id'] . '.json');
                        if ( is_wp_error($response) ){
                            trigger_error('Vimeo video is unavailable.', E_USER_WARNING);
                            $video_metadata['error'] = 'Vimeo video is unavailable.';
                            $this->timer($data['timer_name'], 'end');
                            return $video_metadata;
                        $video_json = $response['body'];
                    return $video_json;
                }, array('provider' => $provider, 'id' => $id, 'timer_name' => $timer_name), HOUR_IN_SECONDS*12);
                if ( !is_array($video_json) ){ //If it is not already an array, decode it from the JSON string
                    $video_json = json_decode($video_json);
                //Check for errors
                if ( empty($video_json) ){
                    if ( current_user_can('manage_options') || $this->is_dev() ){
                        if ( $provider === 'youtube' ){
                            $video_metadata['error'] = 'A Youtube Data API error occurred. Make sure the Youtube Data API is enabled in the Google Developer Console and the server key is saved in Nebula Options.';
                        } else {
                            $video_metadata['error'] = 'A Vimeo API error occurred (A video with ID ' . $id . ' may not exist). Tracking will not be possible.';
                    $this->timer($timer_name, 'end');
                    return $video_metadata;
                } elseif ( $provider === 'youtube' && !empty($video_json->error) ){
                    if ( current_user_can('manage_options') || $this->is_dev() ){
                        $video_metadata['error'] = 'Youtube API Error: ' . $video_json->error->message;
                    $this->timer($timer_name, 'end');
                    return $video_metadata;
                } elseif ( $provider === 'youtube' && empty($video_json->items) ){
                    if ( current_user_can('manage_options') || $this->is_dev() ){
                        $video_metadata['error'] = 'A Youtube video with ID ' . $id . ' does not exist.';
                    $this->timer($timer_name, 'end');
                    return $video_metadata;
                } elseif ( $provider === 'vimeo' && is_array($video_json) && empty($video_json[0]) ){
                    $video_metadata['error'] = 'A Vimeo video with ID ' . $id . ' does not exist.';
                //Build Data
                if ( $provider === 'youtube' ){
                    $video_metadata['raw'] = $video_json->items[0];
                    $video_metadata['title'] = $video_json->items[0]->snippet->title;
                    $video_metadata['safetitle'] = preg_replace('/(\W)/i', '', $video_json->items[0]->snippet->title);
                    $video_metadata['description'] = $video_json->items[0]->snippet->description;
                    $video_metadata['thumbnail'] = $video_json->items[0]->snippet->thumbnails->high->url;
                    $video_metadata['author'] = $video_json->items[0]->snippet->channelTitle;
                    $video_metadata['date'] = $video_json->items[0]->snippet->publishedAt;
                    $video_metadata['url'] = '' . $id;
                    $start = new DateTime('@0'); //Unix epoch
                    $start->add(new DateInterval($video_json->items[0]->contentDetails->duration));
                    $duration_seconds = intval($start->format('H'))*60*60 + intval($start->format('i'))*60 + intval($start->format('s'));
                } elseif ( $provider === 'vimeo' ){
                    $video_metadata['raw'] = $video_json[0];
                    $video_metadata['title'] = $video_json[0]->title;
                    $video_metadata['safetitle'] = preg_replace('/(\W)/i', '', $video_json[0]->title);
                    $video_metadata['description'] = $video_json[0]->description;
                    $video_metadata['thumbnail'] = $video_json[0]->thumbnail_large;
                    $video_metadata['author'] = $video_json[0]->user_name;
                    $video_metadata['date'] = $video_json[0]->upload_date;
                    $video_metadata['url'] = $video_json[0]->url;
                    $duration_seconds = strval($video_json[0]->duration);
                $video_metadata['duration'] = array(
                    'time' => intval(gmdate("i", $duration_seconds)) . gmdate(":s", $duration_seconds),
                    'seconds' => $duration_seconds
                $this->timer($timer_name, 'end');
                return $video_metadata;


    To override this PHP function, use this hook in your child theme or plugin ("my_custom" can be changed):

    add_filter('pre_video_meta', 'my_custom_video_meta', 10, 3); //The last integer must be 1 more than the actual parameters
    function my_custom_video_meta($null, $provider, $id){ //$null is required, but can be ignored
        //Write your own code here
        return true; //Return true to prevent the original function from running afterwords

    You can completely disable this PHP function with a single line actions:

     add_filter('pre_video_meta', '__return_false');