oEmbed

Last updated:

What is oEmbed and how does it work?

oEmbed is a standardized format for allowing content to be embedded on a website. The end-user’s (consumer) browser sends a GET request to a provider's API endpoint with the following parameters.

  • URL (required)
  • maxWidth (optional)
  • maxHeight (optional)
  • format (optional)

The provider then returns structured data in the specified format. This data is then used to display the embedded content on the end-users (consumers) page.  For more information on oEmbed, please refer to the official documentation for the oEmbed specification.

How does HubSpot make use of oEmbed?

We offer two video embed modules (email and pages), one Embed field (Media URL) for custom modules, and one function (email only) that makes use of oEmbed for helping to implement embedded content on your website.

Inside of our modules, you will see a "Media URL" field. This field is equal to the "URL" parameter as mentioned above in the oEmbed specification. Our modules do all the heavy lifting of requesting the information, known as the oEmbed Response, from the URL you specify here and making it available on your site.

When using the oembed() function inside of email, you simply provide a request string that includes the URL, Max Width, and Max Height. You can see an example of this function on our developer documentation.

The oEmbed response explained

An oEmbed Response is JSON data that is returned by a provider (such as YouTube, Flickr, or any others that support oEmbed) which contains various information that is related to the item being fetched. You can view the response parameters on the oEmbed documentation under Section 2.3.4. Each response type (photo, video, link, and rich) has specific parameters that are associated with them.

An example of an oEmbed Response from YouTube is below. If you look closely, you will see that the video type provides an HTML parameter that returns the code you would want to use when embedding the video player on your website.

JSON
// oEmbed Response from YouTube
{
  "title" : "HubSpot Themes Challenge Workshop - Oct 1, 2020",
  "author_name" : "HubSpot Developers",
  "author_url" : "https://www.youtube.com/channel/UCT4M2Uw9rBDpa3JcKaz5UMQ",
  "type" : "video",
  "height" : 113,
  "width" : 200,
  "version" : "1.0",
  "provider_name" : "YouTube",
  "provider_url" : "https://www.youtube.com/",
  "thumbnail_height" : 360,
  "thumbnail_width" : 480,
  "thumbnail_url" : "https://i.ytimg.com/vi/Y4v6cKm2wqE/hqdefault.jpg",
  "html" : "<iframe width=\"200\" height=\"113\" src=\"https://www.youtube.com/embed/Y4v6cKm2wqE?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>"
}

Email only modules

If your module is being used strictly for email and you’re looking to make use of the embed field, we recommend using the embed field in conjunction with the oembed() function. Below is an example of this in use:

HubL
{{ oembed({ url: module.embed_field.oembed_url}) }}

This function returns a dictionary containing the parameters of the oEmbed response for the specified url. Individual parameters can be accessed using dot notation.

Below is an example of this using a YouTube video as a media url source.

HubL
// gets the html code for embedding
{{ oembed({ url: module.embed_field.oembed_url}).html }}

// gets the video title
{{ oembed({ url: module.embed_field.oembed_url}).title }}

// gets the thumbnail URL of the video
{{ oembed({ url: module.embed_field.oembed_url}).thumbnailURL }}

//gets the author name
{{ oembed({ url: module.embed_field.oembed_url}).authorName }}

Page and/or Blog Modules

If your module is being used inside of pages and/or blogs, we recommend making use of javascript to fetch your oEmbed response. This is the recommended approach as when we fetch results through our embed field naturally, we cache them on our side which could cause the oEmbed data to become stale over time.

Below is an example of how you can fetch the results of the oEmbed response using javascript so your results are never stale (cached). 

Note: this all takes place in the HTML + HubL section of the custom module and is using the tags in order to place the correct CSS and JavaScript in the proper areas based on the conditional at the start of the module code.

HubL
{% if module.embed_field.source_type == "oembed" %}
  <div class="oembed_container {% if module.embed_field.size_type == 'auto_full_width' %} oembed_container--full-size{% endif %}" id="oembed_container-{{name}}">
    <div class="iframe_wrapper"
      data-embed-url="{{ module.embed_field.oembed_url }}"
      {% unless module.embed_field.size_type == "auto_full_width" || module.embed_field.size_type == "exact" %}
      data-max-height="{% if module.embed_field.size_type == "auto_custom_max" %}{{ module.embed_field.max_height }}{% endif %}"
      data-max-width="{% if module.embed_field.size_type == "auto_custom_max" %}{{ module.embed_field.max_width }}{% endif %}"
      {% endunless %}
      {% if module.embed_field.size_type == "exact" %}
      data-height="{{ module.embed_field.height }}"
      data-width="{{ module.embed_field.width }}"
      {% endif %}
      >
    </div>
  </div>
  {% require_css %}
    <style>
      .oembed_container {
        display: inline-block;
        height: 100%;
        position: relative;
        width: 100%;
      }

      .oembed_container .iframe_wrapper > * {
        height: 100%;
        left: 0;
        margin: 0 auto;
        position: absolute;
        right: 0;
        top: 0;
        width: 100%;
      }

      .iframe_wrapper {
        height: 0;
        padding-bottom: 56.25%;
        padding-top: 25px;
        position: relative;
      }
    </style>
  {% end_require_css %}
  {% require_js %}
    <script>
      const oembedContainer = document.querySelector('.oembed_container');
      const iframeWrapper = document.querySelector('.iframe_wrapper');
      const url = iframeWrapper.dataset.embedUrl;
      if (url) {
        var request = new XMLHttpRequest();
        var requestUrl = "/_hcms/oembed?url=" + url + "&autoplay=0";
        request.open('GET', requestUrl, true);
        request.onload = function() {
          if (request.status >= 200 && request.status < 400) {
            var data = JSON.parse(request.responseText);

            const maxHeight = iframeWrapper.dataset.maxHeight !== undefined && !iframeWrapper.dataset.maxHeight ? data.height : iframeWrapper.dataset.maxHeight;
            const maxWidth = iframeWrapper.dataset.maxWidth !== undefined && !iframeWrapper.dataset.maxWidth ? data.width : iframeWrapper.dataset.maxWidth;
            const height = iframeWrapper.dataset.height !== undefined && !iframeWrapper.dataset.height ? data.height : iframeWrapper.dataset.height;
            const width = iframeWrapper.dataset.width !== undefined && !iframeWrapper.dataset.width ? data.width : iframeWrapper.dataset.width;

            const el = document.createElement('div');
            el.innerHTML = data.html;
            const iframe = el.firstChild;
            iframeWrapper.appendChild(iframe);

            if (maxHeight) {
              const heightStr = maxHeight.toString(10) + "px";
              oembedContainer.style.maxHeight = heightStr;
              iframe.style.maxHeight = heightStr;
            }

            if (maxWidth) {
              const widthStr = maxWidth.toString(10) + "px";
              oembedContainer.style.maxWidth = widthStr;
              iframe.style.maxWidth = widthStr;
            }

            if (height) {
              const heightStr = height.toString(10) + "px";
              oembedContainer.style.height = heightStr;
              iframe.style.height = heightStr;
            }

            if (width) {
              const widthStr = width.toString(10) + "px";
              oembedContainer.style.width = widthStr;
              iframe.style.width = widthStr;
            }
          } else {
            console.error('Server reached, error retrieving results.');
          }
        };
        request.onerror = function() {
          console.error('Could not reach the server.');
        };
        request.send();
      }
    </script>
  {% end_require_js %}
{% else %}
  <div id="embed_container" class="embed_container">
    <div class="iframe_wrapper">
      {{ module.embed_field.embed_html }}
    </div>
  </div>
  {% require_css %}
    <style>
      .iframe_wrapper {
        height: 0;
        padding-bottom: 56.25%;
        padding-top: 25px;
        position: relative;
      }

      .embed_container {
        display: inline-block;
        height: 100%;
        position: relative;
        width: 100%;
      }

      .embed_container iframe {
        left: 0;
        max-height: 100%;
        max-width: 100%;
        position: absolute;
        right: 0;
        top: 0;
      }
    </style>
  {% end_require_css %}
{% endif %}

Email and Page (or Blog) Modules:

If your module is being used for email in conjunction with page and/or blog, you also have the ability to use the oembed_response object. Once the url is entered in the Media URL field and pulled this data is then cached on our end for use with an oembed_response object. Below is a sample of how you can view the oembed_response dict:

HubL
// outputs the oembed response
{{ module.embed_field.oembed_response }}

You can use dot notation to then grab child elements from the dict. For example when using a YouTube video as a source you can do the following:

HubL
// gets the html code for embedding
{{ module.embed_field.oembed_response.html }}

//outputs the title 
{{ module.embed_field.oembed_response.title }}

// outputs the authors name
{{ module.embed_field.oembed_response.author_name }}

//outputs the thumbnail url (for use in the <img> tag)
{{ module.embed_field.oembed_response.thumbnail_url }}

In order to update the data in the cached oembed_response, you would have to modify your media URL (remove a letter and re-add) and re-apply the changes to your module.

Troubleshooting your Video Embed Module and Embed Field.

When adding "Media URL" links into your video embed module or embed field, you may run into an error that states 

"You can’t embed this media URL for sharing.”

When you receive this error, there are a few things you will want to check with the provider.

  • Does the Asset provider have oEmbed functionality in their platform? The oEmbed official documentation contains a list of providers that support this specification. You may also want to reach out to your provider directly to see if they support this as well.
  • Is the URL "Private"? Many platforms allow users to hide their content from being viewed publicly. For example, if you have an Instagram account that is private, you may not be able to embed content from your posts if it is not available publicly (without requiring the user to sign in to the platform or be a follower/friend).

HTTP Status Codes for oEmbed

The following are status codes that are defined as per the oEmbed Specification:

  • 404 Not Found: There is no response found for the requested URL.
  • 501 Not Implemented: The requested response format cannot be returned.
  • 401 Unauthorized: The requested URL contains a private (non-public) resource.

HubSpot does not have direct control over how third-party providers implement their status codes or how they align to the oEmbed Specification. Because of this, we return either a 404 where applicable or a 400 status code and similar messaging to what you see above. The messaging is contained inside of the response object in the message property.