Special thanks to Cynthia Kiser from the California Institute of Technology and Matt Westcott from Wagtail for their tremendous work on moving this issue along.
As of 24th October 2020, Facebook deprecated the public Instagram oEmbed API. The replacement? Sadly, not public anymore. They now require you to create a Facebook App and authenticate against that.
Wagtail does not yet have support for this new embedding endpoint. It will arrive. They are doing their best. It will take a bit of time though.
Meanwhile, Wagtail allows for customizing embed behavior. So in this post, I will give you everything you need to make Instagram embedding work for your wagtail project again. I hope it will save you some time and make your customers happy as happy as ours.
Facebook App Creation
Before we can go down into code, you first have to create a Facebook App. That in turn requires you to have a Facebook Developer Account, which is a bit beyond the scope of our post here. Once you got yourself this fancy developer account, it's time to continue:
- Open up your apps overview and click Create App (somewhere in the top right corner)
- In the appearing modal, select Manage Business Integration
- Following you see a form. As App Display Name you can pick anything. No one will ever see this app. Also set a contact email and the purpose ("yourself" should be ok). You don't have to select a business manager.
- Survive the anti-robot captcha challenge
- Facebook will show you the Add Products to your App screen. Click Set Up on the oEmbed product.
- There's nothing to setup on the product screen. Instead, we have to head to Settings and then Basic.
- Copy the App ID and the App Secret. We will need these later.
Great. This finishes up all the pre-requirements.
Note: at the time of this writing it does not seem to be a requirement to publish your app via App Review, as long as you only activate the oEmbed product. This could change at any time. If you want to be on the safe side, go to the basic settings and fill out the required fields. Then go to App Review and publish your app. Don't worry, there is no manual review involved. Your app will go live in an instant.
Preparing To Auth
Remember that the new Facebook oEmbed API requires you to authenticate? That's what the App ID and the App Secret from the previous section are for. We should now add them to our Django project. Note that we usually adhere to 12FA Section III, so we store credentials in the environment.
The lines below you will want to put in your settings.py
import os
# ...
OUR_INSTAGRAM_APP_ID = os.environ.get("OUR_INSTAGRAM_APP_ID", "")
OUR_INSTAGRAM_APP_SECRET = os.environ.get("OUR_INSTAGRAM_APP_SECRET", "")
# Bonus point: with the new oembed finder we can also decide to not get the any of the
# facebook js injected into our site.
OUR_OEMBED_INSTAGRAM_OMITSCRIPT = False
Then don't forget to put your credentials into the environment. We usually have a .env
file for this. So, in there:
OUR_INSTAGRAM_APP_ID="0000000000"
OUR_INSTAGRAM_APP_SECRET="noyoudontgettoseemysecret"
Finding the oEmbeds
Friggin' finally! It's time to add the actual code that will fix… (dramatic voice) everything.
I recommend you put this into its own file. I decided to call it igram_oembed_finder.py
. Put this anywhere you like. At Codista we usually have a folder called cms/
where things like this live.
Please keep in mind: the code below is a temporary workaround until the Wagtail Core Team can get this unfortunate change by Facebook accommodated. The code here is not that stellar 😉
import json
import re
from urllib import request as urllib_request
from urllib.error import URLError
from urllib.parse import urlencode
from urllib.request import Request
from django.conf import settings
from wagtail.embeds.exceptions import EmbedNotFoundException
from wagtail.embeds.finders.oembed import OEmbedFinder
class InstagramWorkaroundEmbedFinder(OEmbedFinder):
"""A temporary embed finder that supports new Instagram oEmbed Endpoint.
Sadly Facebook deprecated unauthenticated access to their Instagram oEmbed API.
Until Wagtail is able to integrate this change into core, this finder can workaround
this.
"""
def find_embed(self, url, max_width=None):
endpoint = self._get_endpoint(url)
if endpoint != "http://api.instagram.com/oembed":
return super().find_embed(url, max_width)
endpoint = "https://graph.facebook.com/v8.0/instagram_oembed"
# Work out params
params = self.options.copy()
params["url"] = url
params["format"] = "json"
params[
"access_token"
] = f"{settings.OUR_INSTAGRAM_APP_ID}|{settings.OUR_INSTAGRAM_APP_SECRET}"
if max_width:
params["maxwidth"] = max_width
if getattr(settings, "OUR_OEMBED_INSTAGRAM_OMITSCRIPT", False):
params["omitscript"] = "true"
# Perform request
request = Request(endpoint + "?" + urlencode(params))
request.add_header("User-agent", "Mozilla/5.0")
try:
r = urllib_request.urlopen(request)
except URLError:
raise EmbedNotFoundException
oembed = json.loads(r.read().decode("utf-8"))
# Convert photos into HTML
if oembed["type"] == "photo":
html = '<img src="%s" alt="">' % (oembed["url"],)
else:
html = oembed.get("html")
# Return embed as a dict
return {
"title": oembed["title"] if "title" in oembed else "",
"author_name": oembed["author_name"] if "author_name" in oembed else "",
"provider_name": oembed["provider_name"]
if "provider_name" in oembed
else "",
"type": oembed["type"],
"thumbnail_url": oembed.get("thumbnail_url"),
"width": oembed.get("width"),
"height": oembed.get("height"),
"html": html,
}
embed_finder_class = InstagramWorkaroundEmbedFinder
Using our new finder
The only thing left to do, is to tell Wagtail to use our new finder. Again, above I put this into myproject/cms/igram_oembed_finder.py
. Hence, what I put into my settings.py
will look like this:
# Wagtail Instagram API Deprecation
WAGTAILEMBEDS_FINDERS = [
# Handle facebook's public instagram oembed deprecation.
{"class": "myproject.cms.igram_oembed_finder"}
]
Note: if you do have custom providers, like when adding TikTok support, these should come before. Our igram_oembed_finder
should be the last element.
Taking it for a spin
That's it. You should now be able to embed Instagram posts again.
I've already cursed quite a bit at Facebook for this deprecation, but I also get that they want to identify API endpoint consumers.
If this doesn't work for you or you are having trouble, you can always hit us up on Twitter.