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

Linkify Laravel Package

misd/linkify

misd/linkify is a lightweight PHP library that converts plain-text URLs and email addresses into safe, clickable HTML links. It’s simple to configure and easy to integrate into Laravel or any PHP app to improve user-generated content.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — this is a plain library, not a Laravel package, so there is no service provider, facade, config file, or auto-discovery:

composer require misd/linkify

The whole API is one class, Misd\Linkify\Linkify, with a process($text, array $options = []) method that returns HTML with <a> tags substituted in:

use Misd\Linkify\Linkify;

$linkify = new Linkify();

echo $linkify->process('Ping me at [email protected] or see https://example.com/docs');
// Ping me at <a href="mailto:[email protected]">[email protected]</a>
// or see <a href="https://example.com/docs">https://example.com/docs</a>

Where to look first: the README on GitHub is short and complete. There is nothing else to read — no config, no events, no extension beyond the attr and callback options. The two things worth knowing before you write code: (1) it does not escape anything, and (2) the constructor and per-call options are both arrays with the same shape.

First real use case: rendering user-submitted text (comment bodies, ticket descriptions, profile bios, changelog entries) so bare URLs and emails become clickable, while forcing rel="nofollow" and target="_blank":

$html = $linkify->process($comment->body, [
    'attr' => ['target' => '_blank', 'rel' => 'nofollow noopener'],
]);

Escape the input first — the library does not escape anything (see Gotchas):

return new HtmlString($linkify->process(e($comment->body)));

Implementation Patterns

Bind a configured singleton. Don't new Linkify all over the codebase; register it once so default attributes stay consistent and tests can swap it:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(Linkify::class, fn () => new Linkify([
        'attr' => ['target' => '_blank', 'rel' => 'nofollow noopener noreferrer'],
    ]));
}

Blade directive, for views that render post bodies. Keeps templates readable and locks in the escape-then-linkify order:

// AppServiceProvider::boot()
Blade::directive('linkify', function ($expression) {
    return "<?php echo (new \Misd\Linkify\Linkify(['attr' => ['rel' => 'nofollow']]))->process(e($expression)); ?>";
});
<div class="comment">{!! nl2br($comment->body) !!}</div>

Better still: a LinkifiedText cast, or an HtmlString accessor on the model, so the escaping rule lives in exactly one place and views never call e() themselves.

Per-call overrides for special cases. Options passed to process() take precedence over constructor defaults, so a singleton with sane defaults still allows one-off tweaks:

// Marketing page: allow followed links
$linkify->process($text, ['attr' => ['rel' => 'dofollow']]);

// Internal notes: links are noise, strip them
$linkify->process($text, ['attr' => []]);

Be aware these are top-level key replacements, not deep merges — see Gotchas.

Callback when you need more than attributes. The callback replaces the entire generated anchor, which is how you add icons, tracking params, or wrappers around the link:

$linkify->process($text, [
    'callback' => function (string $text, string $url, bool $isEmail) {
        if ($isEmail) {
            return sprintf('<a href="mailto:%s">%s</a>', e($url), e($text));
        }
        return sprintf(
            '<a href="%s" rel="nofollow noopener" target="_blank" data-track="outbound">%s</a>',
            e($url),
            e($text)
        );
    },
]);

Note the e() calls inside the callback — you own escaping here because your return value is inserted verbatim.

Selective linkification. In 1.1.4 there are only process() public entry points — no processUrls()/processEmails() helpers. If you need URL-only behaviour (e.g., public changelogs where emails shouldn't be auto-linked for spam reasons), piggyback on the callback and return $text unchanged for emails:

$urlOnly = new Linkify([
    'callback' => function (string $text, string $url, bool $isEmail) {
        return $isEmail ? $text : sprintf('<a href="%s" rel="nofollow">%s</a>', e($url), e($text));
    },
]);

Composing with Markdown. Run linkify on the raw text before the Markdown pass, or on paragraphs only — never after Markdown has produced HTML, because the regex will happily rewrite href attribute values into nested anchors.

Testing. Assert against a fixture of known inputs rather than eyeballing output. Cover: trailing punctuation (See https://example.com. — the . should not be inside the href), balanced/unbalanced parentheses (https://en.wikipedia.org/wiki/Foo_(bar)), mixed text with both a URL and an email, addresses with + tags, IDN/Unicode, HTML entities in the input, and XSS payloads like <script> and javascript:alert(1) embedded in the source text. Golden-file tests are cheap here and pay for themselves the first time you upgrade PHP.

Gotchas and Tips

The library does not escape — not the text, not the URL, not the attribute values. This is the headline risk. Always e() input before calling process(), and always e() inside any callback you write. If you skip this, a payload like "><script>alert(1)</script> in a comment body becomes executable in your page. Linkification of untrusted text is an XSS-adjacent operation; treat it as such.

No scheme allow-listing. The regex matches http/https/mailto, but if you feed it HTML that already contains <a href="javascript:...">, or URLs with mixed-case schemes, verify the behavior on your own fixtures. If you need a strict http|https|mailto allow-list, filter after linkifying (or pre-sanitize with a dedicated HTML sanitizer) — this package is not a sanitizer.

attr in process() replaces the constructor's attr array wholesale. It does not merge keys. So new Linkify(['attr' => ['rel' => 'nofollow', 'target' => '_blank']]) followed by a call with ['attr' => ['rel' => 'dofollow']] will lose target. If you need additive behavior, wrap it in a small service that merges the arrays yourself.

Callback output is inserted as-is. No escaping, no attribute injection, no wrapping. Anything you sprintf() in is exactly what lands in the HTML. That's a feature (it's how you add icons and data-*) but also the easiest place to introduce XSS.

Trailing punctuation and parentheses are the classic edge cases. https://example.com., https://example.com), and (https://example.com) are all handled by the included regex, but the behavior varies by version and by what's around the URL. Pin these in tests. If your content is user-generated, users will paste URLs followed by periods, exclamation marks, and closing parens.

Unicode and IDN are "fixed" in 1.1.4 but not a guarantee. The v1.1.4 release notes call out "Fix Unicode handling," which means the regex was patched for a specific reported case — not that it's now fully IDN-aware. If your product links international domains, add explicit fixtures (e.g., https://例え.テスト/) and expect to fall back to a hand-rolled extractor for the cases the regex misses.

Last release was 2017-08-17 and there are effectively zero downstream dependents. On PHP 8.1+ you may hit deprecation warnings or Composer constraint refusals. Run the package's own test suite and a quick install in CI before committing to it. If either is shaky, vendor the single class into your namespace (App\Support\Linkify) and own it — that caps supply-chain and upgrade risk without losing the behavior.

No framework integration. Don't expect a service provider, facade, config publishing, or Blade directive from the package — anything you see in a "Laravel linkify" tutorial that references config/linkify.php or @linkify($text) is an application-side example, not the package. If you need framework integration, you're building it.

Double-processing will corrupt output. Calling process() twice on the same string produces anchors whose href values contain <a href=...> fragments. Route every render through one adapter entry point, and if you have legacy regex linkifiers still in the codebase, remove them in the same PR that switches the call site — don't run both.

Performance is fine for paragraph-sized content, not for unbounded payloads. The regex is linear in practice but can be pathologically slow on adversarial or huge (multi-MB) strings. Enforce a length limit before linkifying user input in hot paths, and consider skipping linkification entirely above e.g. 10 KB.

Email obfuscation is not built in. mailto: links are trivially scraped by spam bots. If emails appear on public surfaces, either render them as plain text (see the "selective linkification" pattern above), apply a Cloudflare email-protection or HTML-entity layer, or use the callback to inject an obfuscated variant.

attr values are not escaped by the library. If you pass ['title' => $userInput] as an attribute, that value lands verbatim inside the tag. Only ever populate attr from trusted config — never from request data.

Escape order is not optional: e() first, then process(). The linkifier emits raw <a> tags that must survive into the page; if you reverse this order you'll double-escape your anchors or, worse, escape only the text and leave the URLs raw. Wrap the two steps in a single Linkifier::process(string $text): string service and never call Misd\Linkify\Linkify directly from controllers, models, or Blade templates — the whole point of the wrapper is that the escaping order and attribute policy have exactly one implementation you can grep for and test.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager