How to embed a post on X on any website
Embed a post on X using the Embed post menu or the free oEmbed endpoint, and fix the WordPress quirk that makes a pasted x.com link do nothing at all.
On this page · 9 sections
The short version
- ▸The fast route: open the post on x.com, click the share icon, choose Embed post, then copy the code from publish.x.com and paste it into your page's HTML.
- ▸Embedding needs no API key. The oEmbed endpoint at publish.x.com/oembed returns ready-made markup for any public post with no developer account and no paid tier, which is a different story from the rest of the X API.
- ▸The markup still carries the old branding. The element is a blockquote with class twitter-tweet and the script it loads resolves to platform.twitter.com, so search-and-replace it to x.com at your peril.
- ▸In WordPress, pasting an x.com link auto-embeds nothing. WordPress core's provider list matches twitter.com URLs only, so swap the domain in the link you paste and the embed appears.
- ▸An embed is a live fetch from X, not a copy. Delete the post or make the account private and the card degrades to plain quoted text with a link.
Quick answer
To embed a post on X, open it on x.com, click the share icon at the bottom of the post, and choose Embed post. You land on publish.x.com with the code already built. Pick a theme, click Copy Code, and paste the block into your page's HTML where you want the post to appear. No developer account, no API key, no cost.
Last updated: September 2026
TL;DR
Embedding a post from X (formerly Twitter) takes about twenty seconds through the share menu, and the code it hands you is a two-line block you can paste anywhere that accepts HTML. The parts worth reading past that: the markup still uses the platform's old naming, which trips up anyone who tidies it, WordPress quietly ignores x.com links because its built-in provider list was written before the rename, and a live embed is a fetch rather than a copy, so it can disappear later. There is also a free public endpoint for doing this at scale, which survived the API paywall that hit almost everything else.
Route one: copy the code from the post itself
This is the path for a single post, and it is the one X documents.
- Open the post on x.com in a browser. The Embed post option does not exist in the mobile apps.
- Click the share icon at the bottom right of the post, the arrow coming out of a box.
- Choose Embed post. A new tab opens at publish.x.com with your post already loaded and the code generated.
- Set the options. You can show or hide the parent post for a reply, and pick light or dark to match your site.
- Click Copy Code, then paste it into your page's HTML, a raw-HTML block in your editor, or an MDX file.
What you get back is a blockquote holding the post text and a link, followed by a script tag. The blockquote is what shows up before anything loads; the script swaps it for the card with the avatar, the like and repost buttons and the media.
Route two: the free endpoint, for anything at scale
Pasting one post is fine. Pasting two hundred is not, and that is what the oEmbed endpoint is for. Send it a post URL and it returns the same markup as JSON:
https://publish.x.com/oembed?url=https://x.com/Interior/status/507185938620219395
The response carries the author name, the author URL, an html field with the ready-to-paste block, a width, and a long cache_age. Any CMS, static site generator or build script can call it and drop the html straight into a template.
Two details make this more useful than it looks. It needs no authentication at all, so there is no key to rotate and no plan to pay for, which is a real outlier now that the main platform API sits behind paid X API tiers. And the cache_age it returns is deliberately enormous, which is the endpoint telling you to store the result rather than call it on every page render. Cache the markup at build time and your pages stop depending on a live call to X.
Useful parameters, all confirmed against the endpoint on 2026-09-17:
| Parameter | What it does |
|---|---|
omit_script=true | Returns the blockquote with no script tag, so you can add the script once per page |
theme=dark | Adds data-theme="dark" to the blockquote for dark backgrounds |
hide_thread=true | Drops the parent post when you are embedding a reply |
maxwidth=400 | Constrains the card, useful in a narrow column |
dnt=true | Adds data-dnt="true", asking X not to use the embed for personalisation |
hide_media=true | Renders the text without the attached photo, video or card |
Why the code still says twitter, and why you should leave it
Here is the part that costs people an afternoon. The element you paste is <blockquote class="twitter-tweet">, and the script tag in it resolves to platform.twitter.com/widgets.js. Not x.com. The rebrand never reached the widget layer.
That naming is not decoration. The widget script scans the page for elements matching blockquote.twitter-tweet and converts them into rendered posts, as X's own embedded posts documentation describes. Rename the class to something tidier like x-post and your embeds silently stay as plain blockquotes forever, because nothing is looking for the new name. The same goes for the data- attributes: they are data-theme, data-conversation, data-cards and data-dnt, and they are read by that script exactly as spelled.
One thing is safe to modernise: the script host. Requesting platform.x.com/widgets.js redirects to the twitter.com host and serves the identical file, which we checked on 2026-09-17. The class name has no such alias.
The WordPress trap: an x.com link embeds nothing
WordPress is supposed to make this trivial. Paste a URL on its own line, and core recognises the provider and expands it into an embed. For years that worked for this platform.
It still does, as long as the link says twitter.com. WordPress core ships a fixed list of oEmbed providers in class-wp-oembed.php, and reading that file on trunk (checked 2026-09-17) every pattern for this platform is written against twitter.com: status URLs, profile URLs, likes, lists, timelines and moments. There is no x.com pattern in the list. So a pasted x.com link matches no provider, WordPress treats it as an ordinary link, and you get a blue hyperlink where you expected a card.
Three ways out, in order of effort:
- Change the domain in the link you paste.
x.com/user/status/123becomestwitter.com/user/status/123. Core matches it, callspublish.twitter.com/oembed, which now redirects topublish.x.com/oembed, and the embed renders. The post itself is unaffected; the URL is an alias. - Use a Custom HTML block and paste the full embed code from publish.x.com. This is the reliable option, and the only one that gives you theme and width control.
- Register the provider yourself with
wp_oembed_add_provider()in a small plugin, matching x.com status URLs to the publish endpoint. Worth it once, if you publish embeds constantly.
The same root cause explains most "my embed suddenly stopped working" reports on other platforms: something in the chain is matching on the old domain and does not recognise the new one.
What breaks an embed later
An embed is a live window into X, not a copy of the post. That distinction only matters on the day something changes.
- The post is deleted. The card stops rendering and the page falls back to the pasted blockquote: the quoted text, the handle, and a dated link to a post that no longer resolves. Ugly rather than broken.
- The account goes private. Embed code is not generated for protected posts, and existing embeds of an account that later locks down stop resolving. If you are weighing that switch for your own account, making your X account private explains what else changes.
- The account is suspended or renamed. A rename keeps the post ID valid, so the embed survives. A suspension does not.
- A reader blocks third-party scripts. Privacy extensions and strict browser modes block the widget host, and those visitors see the blockquote fallback. This is another argument for making the fallback text readable rather than leaving it as a bare link.
If the exact rendering is the point, for example you are writing about something that might get deleted, screenshot it as well. For mock-ups and illustrations where no real post exists, a fake tweet generator produces an image instead, which never depends on a live fetch. Label it clearly as a mock-up; passing a generated image off as a real post is how people end up in a correction.
The cost of an embed, and when to skip it
Each embed pulls in the widget script and then an iframe per post. The script alone is around 90 KB before anything it loads, and a page with a dozen embeds is doing a dozen frame loads that you do not control, on a third-party domain, after your own content has rendered. On a long article that is a visible layout shift and a slower page.
Habits that keep it reasonable:
- Load the script once. Use
omit_script=trueon every embed and add a single script tag at the end of the page. - Lazy-load below the fold. Nothing above the fold should wait on a third-party frame.
- Reserve the space. Set a minimum height on the container so the page does not jump when the card resolves.
- Pass
dnt=trueif your privacy policy commits to not feeding visitor behaviour to third parties through embeds. - Ask whether you need the live card at all. Quoting the text with a link is faster, always renders and never disappears. If you are quoting a post inside X rather than on a website, quoting the post is the native equivalent and behaves quite differently for reach.
One related check while you are in the plumbing: embedding pulls a post from X onto your site, and the reverse trip matters just as much. Whether your own links render a proper preview card when someone posts them on X is a separate system with its own failure modes, and the X card validator guide covers testing that.
Bottom line
For one post, use the share menu, choose Embed post, and copy the code. For many, call publish.x.com/oembed and cache what it returns, because that endpoint is free, unauthenticated and generous with its cache hint. Leave the twitter-tweet class exactly as it is, since the widget script matches on that string. In WordPress, paste a twitter.com link rather than an x.com one, or drop the full code into a Custom HTML block. And treat every embed as a live view of something you do not own: worth a screenshot when the words matter.
If the reason you are embedding posts is that you are building an audience on X, the harder problem is having posts worth embedding. X-Autopilot runs from your own logged-in browser on your Mac rather than a cloud service holding your session, which keeps a smaller footprint than tools that post from a shared IP pool, though no browser-route automation is a sanctioned path under X's automation rules.
Frequently asked
Answers indexed by Google + AI assistants.
How do I get the embed code for a post on X?+
Open the post on x.com, click the share icon at the bottom of the post, and choose Embed post. That sends you to publish.x.com with the code already generated. Set the theme and options you want, click Copy Code, and paste the block into your page's HTML. The menu item only appears on the web, so if you are in the mobile app, copy the post link and paste it into publish.x.com instead.
Do I need an X API key to embed a post?+
No. Embeds run on a separate public endpoint, publish.x.com/oembed, which returns the markup for any public post without a developer account, an API key or a paid tier. That is worth knowing because the main X API moved behind paid plans; embedding did not follow it.
Why does pasting an x.com link into WordPress not embed anything?+
Because WordPress core's oEmbed provider list still matches twitter.com URLs only. Reading the current provider table in WordPress core (class-wp-oembed.php on trunk, checked 2026-09-17), every entry for the platform is written against twitter.com, and there is no x.com pattern. Change the x.com in your pasted link to twitter.com and the auto-embed fires. The endpoint it calls, publish.twitter.com/oembed, now redirects to publish.x.com and still returns valid markup.
Can you embed a post from a private account?+
No. X does not generate embed code for posts from accounts that protect their posts, and the widget cannot render what it is not allowed to fetch. The same applies after the fact: if a public account you embedded goes private, or the post is deleted, the rendered embed stops resolving.
What happens to an embed when the post is deleted?+
The block degrades to the plain HTML that was pasted into your page: a quoted line of text, the author's handle and a link to the original post. It is not a broken image or an empty box, but the styled card is gone. If the exact rendering matters for a piece of writing, take a screenshot as well as embedding.
Is there a way to embed without loading X's JavaScript?+
Yes, with a trade-off. Request the markup with omit_script=true and you get the blockquote without the script tag, so nothing loads from X and the post renders as a styled quote. Add the script once at the end of the page if you want the full card on a page with several embeds, rather than repeating the tag for each one.
- X Developer Platform - Embedded Posts overview, widgets.js behaviour, oEmbed endpoint and data attributes (accessed 2026-09-17)
- X Help - How to embed a post on your website or blog (accessed 2026-09-17)
- WordPress core, class-wp-oembed.php on trunk - provider list matches twitter.com only, endpoint publish.twitter.com/oembed (accessed 2026-09-17)
Product designer and indie hacker. Runs the agent on his own X account every day and writes up what the data shows, including when it's inconvenient.
Follow on X →