I’ve noticed that my articles did not have their carefully chosen thumbnail images displayed when shared on Mastodon and investigated a bit on that. I found two ways of making it work:
Mastodon first tries to build a preview using the OpenGraph meta tags, and some of them were missing. Required og tags are:
- og:type
- og:title
- og:description
- og:image
- og:url
- og:locale
Otherwise, Mastodon fallbacks to fetch the relevant data from the post using the link found in the post’s header:
<link rel="alternate" type="application/json+oembed" href=...>
This returns JSON data generated by WordPress in the following form:
{ "version": "1.0", "provider_name": "colin@colino.net", "provider_url": "https://www.colino.net/wordpress", "author_name": "Colin", "author_url": "/wordpress/archives/author/colin/", "title": "Au revoir Twitter, et merci !", "type": "rich", "width": 600, "height": 338, "html": "<blockquote class=\"wp-embedded-content\" data-secret=\"YsQM2MlHiO\"><a href=\"/wordpress/archives/2022/11/27/au-revoir-twitter-et-merci/\">Au revoir Twitter, et merci !</a></blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"/wordpress/archives/2022/11/27/au-revoir-twitter-et-merci/embed/#?secret=YsQM2MlHiO\" width=\"600\" height=\"338\" title=\"« Au revoir Twitter, et merci ! » — colin@colino.net\" data-secret=\"YsQM2MlHiO\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"></iframe><script type=\"text/javascript\">\n/*! This file is auto-generated */\n!function(c,l){\"use strict\";var e=!1,o=!1;if(l.querySelector)if(c.addEventListener)e=!0;if(c.wp=c.wp||{},c.wp.receiveEmbedMessage);else if(c.wp.receiveEmbedMessage=function(e){var t=e.data;if(!t);else if(!(t.secret||t.message||t.value));else if(/[^a-zA-Z0-9]/.test(t.secret));else{for(var r,s,a,i=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),n=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),o=0;o<n.length;o++)n[o].style.display=\"none\";for(o=0;o<i.length;o++)if(r=i[o],e.source!==r.contentWindow);else{if(r.removeAttribute(\"style\"),\"height\"===t.message){if(1e3<(s=parseInt(t.value,10)))s=1e3;else if(~~s<200)s=200;r.height=s}if(\"link\"===t.message)if(s=l.createElement(\"a\"),a=l.createElement(\"a\"),s.href=r.getAttribute(\"src\"),a.href=t.value,a.host===s.host)if(l.activeElement===r)c.top.location.href=t.value}}},e)c.addEventListener(\"message\",c.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",t,!1),c.addEventListener(\"load\",t,!1);function t(){if(o);else{o=!0;for(var e,t,r,s=-1!==navigator.appVersion.indexOf(\"MSIE 10\"),a=!!navigator.userAgent.match(/Trident.*rv:11\\./),i=l.querySelectorAll(\"iframe.wp-embedded-content\"),n=0;n<i.length;n++){if(!(r=(t=i[n]).getAttribute(\"data-secret\")))r=Math.random().toString(36).substr(2,10),t.src+=\"#?secret=\"+r,t.setAttribute(\"data-secret\",r);if(s||a)(e=t.cloneNode(!0)).removeAttribute(\"security\"),t.parentNode.replaceChild(e,t);t.contentWindow.postMessage({message:\"ready\",secret:r},\"*\")}}}}(window,document);\n</script>\n", "thumbnail_url": "/wordpress/wp-content/uploads/image-59.png", "thumbnail_width": 600, "thumbnail_height": 305 }
Mastodon wants nothing to do with type: rich oembed contents, explaining why in its sourcecode:
case @card.type when 'link' @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present? when 'photo' return false if embed[:url].blank? @card.embed_url = (url + embed[:url]).to_s @card.image_remote_url = (url + embed[:url]).to_s @card.width = embed[:width].presence || 0 @card.height = embed[:height].presence || 0 when 'video' @card.width = embed[:width].presence || 0 @card.height = embed[:height].presence || 0 @card.html = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED) @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present? when 'rich' # Most providers rely on <script> tags, which is a no-no return false end
This is very much understandable, nobody sane wants to inject javascript as returned by WordPress’s oembed generator. Fortunately, we can fix it by adding a filter in our child theme’s functions.php file, removing the html field and setting type to link :
/* Return link instead of rich oembed data */ function filter_oembed_response_data($data) { if ($data['type'] === 'rich') { $data['type'] = 'link'; unset($data['html']); } /* While we're at it, remove single quotes html entities */ $data["title"] = str_replace("’","'", $data["title"]); return $data; } add_filter('oembed_response_data', 'filter_oembed_response_data', 11, 4);
And finally, Mastodon will not use the image if it is bigger than 2MB. WordPress up to at least 6.6.2 sends full-size images URLs as thumbnail URLs, which can quickly cross this threshold. See #62094.
Before/after: