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

Purify Laravel Package

stevebauman/purify

A Laravel wrapper for HTMLPurifier that sanitizes user input. Clean strings or arrays of HTML through a simple Purify facade, with publishable configuration, caching, and custom profiles for flexible, safe markup filtering.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. Purify is a thin, idiomatic Laravel wrapper around ezyang/htmlpurifier, the long-standing PHP HTML sanitizer. It contributes three things a TPM should care about: a service provider + facade (Purify::clean()), a named-configuration model (configs.default, configs.comments, etc.) that mirrors Laravel's database/mail conventions, and Eloquent casts (PurifyHtmlOnGet / PurifyHtmlOnSet) that let sanitization be enforced at the model boundary rather than scattered across controllers. For a Laravel app that accepts user-authored HTML (CMS bodies, comments, rich-text editor output, imported email/feed content), this is a very small architectural surface: one Composer dependency, one config file, one optional cache path. It does not introduce queues, middleware, new storage, or a service boundary. Fit is strongest for apps already committed to Eloquent as the read path for rendered HTML; it is weaker for APIs that emit JSON consumed by a separate rendering tier, where sanitization must be applied explicitly before serialization.

Integration feasibility. Low effort. composer require stevebauman/purify, publish the provider config, then either call the facade at the boundary or attach a cast. The README documents the two supported enforcement points (on read, recommended; on write, optional) and ships ready-made definitions for HTML5 and Basecamp Trix, which removes the most common blocker (sanitizer stripping editor-specific markup). Named configs mean one package can serve multiple trust levels — e.g. a permissive posts config and a locked-down comments config — without forking code. Dynamic per-call overrides exist but are explicitly not merged with the default, which is a footgun worth encoding in team conventions. Estimated effort for a first production slice: 1–3 engineering days including tests; per-additional-config work is hours, not days.

Technical risk. Concentrated in three areas:

Risk Severity Notes
Upstream HTMLPurifier posture Medium–High The underlying library is mature but effectively feature-frozen and slow-moving relative to PHP/Laravel release cadence. The wrapper inherits that trajectory.
Sanitizer bypass via permissive definitions High Custom Definition/CssDefinition classes are the intended extension point and also the primary way teams reintroduce XSS (widening data-*, contenteditable, style). Any change here is a security review item, not a routine PR.
Silent content mutation Medium Purifier strips rather than rejects. Legitimate markup disappearing after a config change is a user-visible regression that surfaces as bug reports, not alerts.
Bus factor Medium Single-maintainer wrapper; the README's upgrade guides show the wrapper has absorbed breaking upstream changes before, which is good, but the release cadence depends on one person.
Signal quality of the metadata Low confidence 537 stars with 0 listed dependents and a score of ~20 sits oddly against a package this commonly used; treat the popularity/opportunity scores as unreliable inputs and validate adoption independently.

Key questions to resolve before committing.

  • What is the upstream ezyang/htmlpurifier maintenance commitment, and does the wrapper pin a version range that will accept PHP 8.4 / Laravel 12+?
  • Which enforcement point do we standardize on — read (cast) or write (form request / cast-on-set)? Mixing both creates double-sanitization and confusing diffs.
  • Do we have any content type that genuinely requires custom definitions (Trix, ProseMirror, embedded media)? If yes, who owns the security review of those definitions?
  • Is there a regression corpus of real stored HTML we can run through the purifier to measure stripping before and after config changes?
  • What is the latency budget per rendered record, and do any list endpoints render N records per request?

Integration Approach

Stack fit. Requires PHP ≥ 7.4 and Laravel ≥ 7.0 — trivially satisfied by any maintained Laravel application. No database migrations, no queue workers, no frontend coupling. The optional serializer cache uses either the filesystem or a Laravel cache store, so it slots into existing config/filesystems.php and config/cache.php conventions rather than inventing new infrastructure. This is a "composer require and go" package, not an integration project.

Migration path. Because the recommended practice is sanitize-on-output, no data migration is required. Existing rows stay as-is; the purifier becomes a rendering-time filter. That is the single most important sequencing fact: it decouples the security fix from a backfill, and it means rollback is a config revert rather than a data restore.

Suggested sequence:

  1. Inventory every field that renders HTML (columns, JSON blobs, third-party imports) and classify by trust level and editor type.
  2. Baseline configs.default to a deliberately restrictive allowlist; ship it behind a feature flag or config toggle so it can be disabled in one deploy.
  3. Enforce on read via PurifyHtmlOnGet on the identified models (or a shared base/trait), keeping the facade available for non-Eloquent paths.
  4. Add named configs per surface (comments, posts, emails) instead of one global rule set.
  5. Add custom definitions only where an editor demands it, each with a named owner and a test fixture.
  6. Enable the serializer cache with a dedicated disk/path — never the default app cache — and wire php artisan purify:clear into the deploy pipeline only when definitions change.
  7. Instrument before declaring done: log strip-rate deltas and add a smoke test that renders representative content through each config.

Compatibility and upgrade posture. The package has absorbed upstream breaking changes across major versions (v4→v5→v6), and the documented upgrade guides cover the config-reshaping that v5 introduced. Plan for an annual upgrade review tied to Laravel's major release cycle, and pin the version explicitly (^6.x) rather than floating. Watch for the deprecated settings array pattern if any legacy install is being brought forward — that shape was replaced by configs.

Sequencing risk to call out. Custom definitions should land after the baseline is in production and measured. Shipping a permissive Trix definition and a strict default in the same release makes it impossible to attribute stripping regressions to either change.

Operational Impact

Maintenance. The wrapper is small and low-churn; expect dependency-bump PRs rather than feature work. The real maintenance load is configuration maintenance: allowlists, custom definitions, and CSS definitions are security-relevant code that drifts as product requirements change (a new editor feature, an embedded video, a text-align: start value not in the default CSS definition). Budget for a named owner of config/purify.php and any Definitions/* classes, plus a review gate on changes to those files — equivalent to how you'd treat auth config.

Support. Community-supported, MIT, single primary maintainer. There is no vendor SLA. Internal mitigation: keep the integration shallow enough that replacing the wrapper with direct HTMLPurifier calls (or with a different sanitizer entirely) is a contained change. The facade and casts make this realistically a day or two of work, which is the right amount of coupling for a security-critical dependency. Upstream HTMLPurifier issues are the ones most likely to block you, not wrapper issues.

Scaling. Sanitization is CPU-bound string parsing, executed per rendered value. Two distinct cost profiles:

  • Definition serialization — mitigated by the serializer cache; without it, definitions are re-serialized every request. Always cache in production and give Purify a dedicated cache store or filesystem path so purify:clear never flushes unrelated application cache. The README flags exactly this hazard.
  • Per-value purification — not cached by this package. A page rendering 50 comments pays 50 parses. For hot read paths, options are: sanitize-on-write for immutable content, cache the rendered HTML fragment, or accept the cost after benchmarking. Measure before optimizing; single-value cost is typically sub-millisecond for modest documents, but pathological inputs (deeply nested or very large HTML) can be much worse and should be bounded by input-length validation upstream.

Failure modes to design for.

Failure Symptom Mitigation
Serializer cache stale after definition change Newly allowed tags still stripped Run purify:clear in the deploy step whenever definitions/css-definitions change
Cache path not writable / disk full Exceptions on first render after deploy Dedicated path with permissions checked in deploy smoke test
Over-permissive definition XSS in stored content Security review gate + adversarial test corpus per definition
Over-restrictive default Legitimate markup silently dropped Strip-rate logging, before/after diff on a real content sample
Dynamic config() not merged with defaults Rules unexpectedly absent Team convention: use named configs, reserve config() for throwaway cases
Sanitizing on both write and read Content degradation over successive saves Pick one enforcement point per field and document it
Malformed/invalid UTF-8 input Purifier drops or mangles content Validate encoding at the input boundary

Ramp-up. A Laravel developer can be productive in under an hour: facade call, one cast, done. The skill gradient is entirely in the definition layer — HTMLPurifier's HTML.Allowed, CSS.AllowedProperties, and the HTMLPurifier_HTMLDefinition API are idiosyncratic and thinly documented outside the upstream config reference. Plan for one engineer to become the internal definition owner, and pair that with a fixture-based test suite so the knowledge is encoded rather than personal. Given the low integration cost and the alternative (hand-rolled regex sanitization, which is reliably worse), the build-vs-buy calculus favors adopting this package for any Laravel app that renders user HTML — with the caveat that you are adopting HTMLPurifier's security model and lifecycle, and should validate its upstream trajectory as part of the decision rather than treating the wrapper's star count as the signal.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json