embed/embed: one PHP object for oEmbed, OpenGraph and scraped page metadata
Get info from any web service or page
At a glance
- What is it?
- Embed is a PHP library that turns any page URL into a single object carrying title, image, author, feeds and embed code, filled from oEmbed endpoints, OpenGraph and Twitter card tags, JsonLD and raw HTML parsing. Its maintainer has posted a hand-over notice at the top of the README, which is the first thing to read before adopting it.
- Who is it for?
- Embed fits PHP teams that need link previews, media cards or a metadata feed without writing a scraper per site, and it is a poor fit for work that needs guarantees about uptime and source priority, because the README documents no timeout, retry or caching behaviour and never says which extraction source wins when a page ships oEmbed, OpenGraph and JsonLD at once.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Turning any URL into title, image and embed code
Embed does one job on the PHP side: take a page address and hand back a normalized set of metadata fields. Call get() with a url and read properties straight off the returned object: title, description, url for the canonical address, keywords, image for the thumbnail or main picture, authorName, authorUrl, cms, language, languages, providerName, providerUrl, icon, favicon, publishedTime and feeds. Four properties carry prebuilt markup rather than plain text. code->html is the snippet to drop into a page for the image or video, code->width and code->height give the exact dimensions when the source declares them, and code->ratio is commented as the percentage of height over width, to be used with paddings when real dimensions are missing. That comment is vague about its unit, since a ratio used for padding is normally a fraction, and no sample value appears anywhere. The provider fields matter when a single feed mixes sources: YouTube, Twitter and Instagram pages all fill the same property names while recording which site each item came from. The favicon field is documented as an .ico file or a png of up to 32x32px, which is a small hint at what it will and will not return.
get() hides four extraction strategies behind one object
One call can succeed through several routes. Embed reads oEmbed endpoints, OpenGraph and Twitter card meta tags, JsonLD linked data, and falls back to parsing the html itself, with site specific adapters adding handling for archive.org, github and facebook. Every route writes into the same info object, so calling code never branches on where the data came from. That is the useful part and the risky part at once. What the documentation never states is precedence: when a page ships an oEmbed endpoint, an og:title tag and a JsonLD block simultaneously, the order between them is undeclared, and neither is the behaviour when a site answers with a bot wall, a paywall or a redirect chain. For anyone caching results this matters, because a field can switch source with no visible change in your own code, and a title you stored last month is not guaranteed to come from the same place today.
getMulti() batches urls for concurrent fetching
Bulk work goes through getMulti(), which takes a variable number of urls and returns one info object per url, ready to walk with a foreach loop. The documented example passes three urls of deliberately different shapes: a YouTube watch page, a single Twitter status, and a Wikipedia article about Tordoia. Mixed inputs are the point of the example, since the same property names have to hold across video, social and encyclopedia pages. Nothing states how many requests run at once, what timeout applies, or whether a failure on one url aborts the batch or returns an empty object in its place. For a small link preview form that does not matter. For a backfill over tens of thousands of urls, those three answers have to come from reading the source before you commit to it.
QueryResult and Metas turn xpath into typed accessors
Anything the standard fields miss stays reachable, because the fetched document is kept on the result. $info->getDocument() returns a document object wrapping a DOMDocument, which can be cast to a string for the raw markup or queried with link('image_src') to read a head link href. XPath goes through select(), which always yields an Embed\QueryResult rather than a bare node list, and that object carries filter() for callbacks plus typed readers: str() and strAll() for text, int() for integers, url() which rewrites relative hrefs into absolute ones, and node() or nodes() for the underlying DOMElement list. The Metas object works the same way over the head tags, keyed by the name, property or itemprop attribute with the value read from content, so og:title, og:description, og:video:width and og:url are reachable through get, str, html, int and url. getLinkedData() applies that same accessor family to the JsonLD payload.
oEmbed query parameters pass through setSettings
Providers that gate parts of their oEmbed response are reached through settings on the returned object. The key oembed:query_parameters takes a map of query values, and the Instagram example in the README sets hidecaption to true so the caption stays out of the answer. This is the only documented channel for provider specific options. Note where it sits: the settings call is made on an object that get() has already returned, and the docs do not say whether the endpoint is then requested again with the new parameters or whether the cached first response is reused. No other settings key appears, so nothing describes tuning for the scraping path or for adapter behaviour either.
Composer, PHP 7.4 and an auto-detected PSR-17 implementation
Three prerequisites are listed: PHP 7.4 or newer, the Curl library installed, and a PSR-17 implementation. The last one is not an extra install step, because four implementations are detected automatically, laminas/laminas-diactoros, guzzle/psr7, nyholm/psr7 and sunrise/http-message, and having any one of them present is enough. The package comes from Packagist under a different name than the repository:
$ composer require embed/embedA demo page is bundled, which is the fastest way to see extraction against real sites before wiring a host application:
php -S localhost:8888 demo/index.phpThe first real call needs three statements, one object construction and one fetch:
use Embed\Embed;
$embed = new Embed();
$info = $embed->get('https://www.youtube.com/watch?v=PP1xn5wHtxE');Reading the result is direct property access with no separate parsing step:
$info->title;
$info->image;
$info->code->html;Older runtimes are pointed elsewhere: PHP 5.5 through 7.3 users are sent to the 3.x version. At the repository root sit phpstan.dist.neon, phpunit.xml.dist and .php_cs.dist, so static analysis, tests and a style config are part of the source, even though no test command is documented for people who consume the library.
The package name, the repo path and the 3.x branch disagree
One library carries three names. The repository is php-embed/Embed, the Packagist package that composer installs is embed/embed, and the older branch for legacy PHP is linked at a different GitHub path entirely, oscarotero/Embed. Composer wants the package name, so `composer require embed/embed` resolves while typing the repository name into require does not, a search that lands on the wrong path ends up reading the older 3.x code. The repository also leaves its homepage field empty, so there is no canonical site beyond the GitHub page itself, and its topic tags are embeds, oembed, opengraph, scraping and twitter-cards, which confirms the extraction sources are the product rather than any single site integration. The repository is not archived and the last push was on 2026-07-08, but the gap between the 4.4.17 tag on 2025-05-13 and 4.4.18 on 2026-06-19 runs over a year, and the README opens with a notice that the author is looking for a maintainer and will keep accepting community pull requests without doing active work.
The Instagram example writes $result and then reads $info
Two details in the examples deserve a pause before anyone copies them. The Instagram snippet assigns the fetch to $result and calls setSettings on that variable, then reads the oEmbed object from $info, a name that snippet never defines, so PHP has nothing bound to $info on the last line and the call fails. The LinkedData example stops in the middle of a comment right after $ld->url('url'), leaving the accessor list for that object unfinished, so the JsonLD section is the one place where the documented surface is visibly truncated. Neither issue damages the library itself, but both land on whoever wires it up first. On licensing, the project ships under MIT, which is permissive enough to bundle inside a proprietary application without relicensing your own code, while the obligations in the licence text remain with whoever distributes it.
Editorial conclusion
Embed fits PHP teams that need link previews, media cards or a metadata feed without writing a scraper per site, and it is a poor fit for work that needs guarantees about uptime and source priority, because the README documents no timeout, retry or caching behaviour and never says which extraction source wins when a page ships oEmbed, OpenGraph and JsonLD at once. Check the hand-over situation first, since the maintainer will not be doing active work, and decide how you would keep the site adapters working.
Frequently asked questions
How do I install php-embed/Embed in a PHP project?
Run composer require embed/embed. The stated requirements are PHP 7.4 or newer, the Curl library installed, and a PSR-17 implementation, where laminas/laminas-diactoros, guzzle/psr7, nyholm/psr7 and sunrise/http-message are detected automatically. Projects on PHP 5.5 through 7.3 are sent to the 3.x version instead.
Is php-embed/Embed still being maintained?
The README opens with a notice from the author saying they no longer have the time or motivation to continue, and are looking for someone to take the project on. Community pull requests are still accepted, and the last push was on 2026-07-08.
How do I get an Instagram oEmbed without the caption?
Pass the key oembed:query_parameters to setSettings with hidecaption set to true on the object returned by get(). It is the only documented provider specific option.
What does getMulti() do in php-embed/Embed?
It loads several urls asynchronously and returns one info object per url, which you can iterate with foreach. The documented example mixes a YouTube watch page, a Twitter status and a Wikipedia article. No concurrency limit, timeout or retry behaviour is documented.
Can I extract data the standard fields do not cover?
Yes. $info->getDocument() exposes the stored html, and select() runs XPath queries that return an Embed\QueryResult with str, strAll, int, url, node and nodes readers plus a filter callback. getMetas() and getLinkedData() expose head meta tags and JsonLD data through the same accessor style.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/php-embed-embed)Was this useful?