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

Purifier Laravel Package

larahook/purifier

Laravel wrapper for HTMLPurifier that cleans and sanitizes untrusted HTML before it reaches your views. Filter user-generated content through a simple, configurable API to strip XSS and other malicious markup while preserving safe formatting.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal setup for a Laravel app:

composer require larahook/purifier
  • Laravel 5.5+: the service provider and Purifier facade are auto-discovered. For older Laravel (5.4 and below) register Larahook\Purifier\PurifierServiceProvider in config/app.php and add the facade alias 'Purifier' => Larahook\Purifier\Facades\Purifier::class.
  • Publish the config so you can tune profiles:
php artisan vendor:publish --provider="Larahook\Purifier\PurifierServiceProvider" --tag=config
  • Look first at config/purifier.php. That file is the real API surface of this package — profiles (often default and custom) mapping to HTMLPurifier directives such as HTML.Allowed, HTML.ForbiddenElements, URI.AllowedSchemes, HTML.SafeIframe, URI.SafeIframeRegexp, and Cache.DefinitionImpl.
  • First use case: sanitize user-submitted rich text before you store or render it. In a controller or Form Request:
use Larahook\Purifier\Facades\Purifier;

$clean = Purifier::clean($request->input('body'));

Then render the already-sanitized value with {!! $post->body !!} instead of {{ }} (which would double-escape).

A good second step is to read the package's service provider and facade source — it is small, and the method signatures (whether clean() accepts a second $config argument, whether a purify() helper or Blade directive exists) tell you exactly what this fork adds over stock HTMLPurifier.

Implementation Patterns

Form Request + sanitization before persistence. Normalize input at the boundary, so every downstream consumer (APIs, exports, admin lists) sees clean HTML:

protected function prepareForValidation(): void
{
    $this->merge([
        'body' => Purifier::clean($this->input('body', '')),
    ]);
}

Multiple profiles for multiple contexts. Don't run one maximal config everywhere. A "comments" profile should allow only p, a, strong, em, ul, ol, li and no style; a "cms" profile can allow headings, tables, images, and safe iframes. Keep both in config/purifier.php and call Purifier::clean($html, 'cms').

Model-level mutators or casts. For content-heavy apps, sanitize inside the model so any write path is covered:

public function setBodyAttribute($value): void
{
    $this->attributes['body'] = Purifier::clean($value, 'cms');
}

Be deliberate here — a cast hides cost and makes batch imports slow; a mutator is fine for interactive requests.

Sanitize on output as a safety net. If you have legacy rows that predate the package, wrap the render site rather than migrating the data:

{!! Purifier::clean($post->body, 'cms') !!}

You can do both (sanitize on write and on read) during a transition, then drop the read-side call once the data is clean.

Sanitize after Markdown, not before. HTMLPurifier only understands HTML. If you pass Markdown through it first, you'll lose the formatting before the Markdown parser runs, or worse, allow the parser to reintroduce unsafe HTML. Correct order: Markdown → HTML → Purifier::clean().

Custom validation rule. Wrap sanitization in a rule when you want to reject instead of silently strip:

Validator::extend('clean_html', fn ($attr, $value) =>
    trim(Purifier::clean($value)) === trim($value)
);

This is useful for editor integrations where silent stripping confuses authors.

API responses. For a JSON API serving rich content, sanitize once at write time and store the clean string; sanitizing on every response is a measurable hit on HTMLPurifier's already-heavy parser.

Integration with WYSIWYG editors. Tune the profile to the editor's allowed output (HTML.Allowed, CSS.AllowedProperties, AutoFormat.AutoParagraph) so what the editor produces survives round-trips. Run a fixture of representative editor output through clean() in a test to lock the behavior.

Gotchas and Tips

  • HTMLPurifier is slow by default. It serializes its definition to a cache directory. Keep Cache.DefinitionImpl enabled and point Cache.SerializerPath at a writable path (typically under storage/app). If the directory isn't writable, you'll usually get a warning plus a silent performance cliff — check logs after first deploy.
  • Stale definitions after config changes. If you edit HTML.Allowed and see no change, HTMLPurifier is serving an old serialized definition from its cache. Clear that cache directory (or bump HTML.DefinitionID/DefinitionRev) as part of your config-change checklist.
  • It removes rather than escapes. clean('<script>alert(1)</script>') returns an empty string, not &lt;script&gt;.... This is correct for rich text but wrong if you wanted the markup displayed literally — use e()/{{ }} for that.
  • Iframes need two directives. HTML.SafeIframe alone is not enough; you must also set URI.SafeIframeRegexp to the embed hosts you trust. Otherwise YouTube/Vimeo embeds vanish and authors file bugs.
  • Images and links need scheme allowlists. URI.AllowedSchemes governs href/src. Relative URLs are generally fine, but custom protocols (e.g., tel:, mailto:) must be listed explicitly.
  • CSS is a separate surface. Allowing style requires CSS.AllowedProperties and friends. If you allow arbitrary style, you're reopening a class of attacks — prefer classes over inline styles in the editor output.
  • null/empty input. Wrap calls with a null coalesce (Purifier::clean($html ?? '')) rather than relying on the wrapper to normalize types; behavior across forks varies.
  • Extension points: HTMLPurifier's own HTML.DefinitionID + custom HTMLPurifier_Config definitions let you add bespoke attributes; most Lavarel wrappers let you pass a profile name or a config array as the second argument. Reach for a custom definition instead of patching the vendor package.
  • Low adoption (2 stars, 0 dependents): pin the version in composer.json, and keep an integration test that asserts your profiles allow and strip the right things. If the fork goes stale, migrating to stock ezyang/htmlpurifier is a service-provider change, not a rewrite — so avoid spreading Purifier:: calls across 40 files.
  • Testing tip: assert on both outcomes — a safe fixture comes back unchanged, an unsafe fixture (<script>, onerror=, javascript: URL) comes back scrubbed. This catches config regressions far better than a single happy-path 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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings