Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Link Preview Laravel Package

dusterio/link-preview

A Laravel package for generating rich link previews from URLs. It extracts Open Graph and page metadata such as title, description, and image, letting you render Facebook-style link cards in your application.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require dusterio/link-preview

On Laravel 5.5+ the service provider is auto-discovered. On older versions, register the package provider in config/app.php. If the package ships a config file, publish it with php artisan vendor:publish and check it for timeout / user-agent options before doing anything else.

The whole API is one call:

use Dusterio\LinkPreview\Client;

$preview = (new Client('https://laravel.com'))->getPreview('og');

$preview->getUrl();
$preview->getTitle();
$preview->getDescription();
$preview->getCover();   // the preview image URL

getPreview('og') asks for the Open Graph generator; the client fetches the page, parses the HTML, and hands back a Preview value object.

Where to look first, in this order:

  1. src/Client.php — the entrypoint. See how it wires a parser to a generator, and what it throws.
  2. src/Parsers/ — the class that actually makes the HTTP request and parses the DOM. This is where timeouts and user-agent live.
  3. src/Generators/ — how raw meta tags become title/description/cover.
  4. src/Models/Preview.php — the DTO you will end up serialising to JSON.

First use case: a "paste a URL" field in a comment box or admin panel that renders a Slack-style card. Get that working end-to-end (input → Client → array → Blade partial) before touching caching or queues — it will immediately show you which sites return nothing.

Implementation Patterns

Wrap it in a service, never call it from a controller or a Blade view.

final class LinkPreviewService
{
    public function for(string $url): ?array
    {
        try {
            $preview = (new Client($url))->getPreview('og');

            return [
                'url'         => $preview->getUrl() ?: $url,
                'title'       => $preview->getTitle(),
                'description' => $preview->getDescription(),
                'image'       => $preview->getCover(),
            ];
        } catch (\Throwable $e) {
            report($e);
            return null;
        }
    }
}

Returning null on failure (rather than throwing upward) keeps the caller simple and makes it obvious where the "no preview available" branch is rendered.

Cache aggressively — the same handful of URLs get pasted repeatedly. Cache::remember('preview:'.sha1($url), now()->addDay(), ...) is enough for most apps and cuts remote requests by an order of magnitude. Cache the null failure case too, but with a short TTL (5–15 minutes) so a temporarily-down host doesn't get retried on every keystroke.

Queue anything user-triggered. A form submission should dispatch a job and return immediately; the job fills the cache. The UI then polls or renders a placeholder.

dispatch(new FetchLinkPreview($url));

Persist the resolved array, not the object. Store it in a json column on the model that owns the link (comment, bookmark, ticket) so the card survives the source site changing or disappearing, and so listing pages never make outbound calls.

Render one Blade partial and reuse it everywhere — a _card.blade.php that gracefully degrades to a bare link when title or image is missing. Escape everything with {{ }}; the title comes from a third party.

Layer a fallback. Try the Open Graph generator first; if the title comes back empty, fall back to a plain meta-tags extraction or to the URL's host as the display label. Sites without OG tags are common enough that you will ship this on day two.

Extension point: if you need a tag the package doesn't surface (e.g. og:video), copy the pattern of the shipped generator — read the meta tags from the parsed document, populate a Preview, and register your generator with the client. Keep that class in your app, not in vendor/.

Gotchas and Tips

  • It's synchronous and blocking. getPreview() makes a live HTTP request on the calling process. On a web request that's a slow-user-visible hang; behind a queue it's fine.
  • SSRF is your problem, not the package's. Any user-supplied URL can point at 169.254.169.254 or an internal host. Validate the scheme, resolve DNS and reject private/link-local ranges, or route fetches through a proxy you control.
  • No JavaScript execution. React/Vue marketing sites return an empty shell; you'll get a blank preview. Detect that case (empty title) and fall back to the hostname, or use a headless browser for the handful of domains that matter.
  • Set explicit timeouts. Default Guzzle behaviour will happily wait a long time on a slow host and tie up a queue worker. Configure connect_timeout and timeout to a few seconds each.
  • Watch the dependency graph on PHP 8.1+. Last release was March 2022; check composer why-not guzzlehttp/guzzle ^7 and run the test suite before upgrading PHP. Pin the package version in composer.json rather than floating.
  • og:image can be relative or protocol-relative (//cdn.example.com/x.jpg). Normalise to an absolute URL before storing, or your <img> breaks.
  • Encoding: non-UTF-8 pages produce mojibake in titles. Run retrieved strings through mb_convert_encoding($s, 'UTF-8', 'auto') if you see it.
  • Some sites 403 unknown user agents. If a specific domain always fails, check the UA the parser sends before assuming the package is broken.
  • Debugging tip: when a preview is empty, dump the raw HTML the parser received first. Nine times out of ten the tags are absent or the request was redirected to a consent/captcha page — not a bug in the generator.
  • Treat it as a thin wrapper you may need to fork. 127 stars and a 2022 release mean low maintenance; keeping the call behind your own LinkPreviewService interface lets you swap in embed/embed or a headless fetcher later without touching call sites.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo