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

Laravel Text Guard Laravel Package

overtrue/laravel-text-guard

A Laravel package for guarding text input against sensitive or banned words. It offers configurable filtering and validation to help detect and block unwanted content in forms, comments, and other user-generated text with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require overtrue/laravel-text-guard:^1.0
    

    Version 1.0.4 only relaxes the framework constraint to include Laravel 12/13 — there are no API, config, or dictionary-format changes versus 1.0.3. Treat it as a drop-in bump: pin ^1.0.4 if you develop against Laravel 13, otherwise ^1.0 remains fine and the upgrade carries no migration work.

  2. Publish the config (the only setup step that usually matters):

    php artisan vendor:publish --tag=text-guard-config
    

    This drops config/text-guard.php in place. That file is the first place to look — it is where the word list (inline array and/or dictionary file path), replacement character, and any caching options live.

  3. Where to look next, in order

    • config/text-guard.php — words, dictionary file path, replacement strategy.
    • The service provider's singleton binding — tells you whether the trie is rebuilt per call or reused, which drives all of your performance decisions.
    • The README section on dictionary files — inline array vs. file-based loading is the single biggest operational fork.
  4. First useful case: guarding user-generated text

    use Overtrue\LaravelTextGuard\Facades\TextGuard;
    
    $guard = TextGuard::make();
    
    $guard->check('some user submitted text'); // bool
    $guard->filter('some user submitted text'); // masked string
    

    Start with comments, reviews, nicknames, and chat messages — the classic places where a small sensitive-word list plus a mask is enough and a third-party moderation API is overkill.

Implementation Patterns

Validation rule: reject rather than mask. For fields that must be clean (usernames, titles), fail the request instead of silently rewriting user input:

use Overtrue\LaravelTextGuard\Facades\TextGuard;
use Illuminate\Validation\ValidationException;

class Clean implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (TextGuard::make()->check((string) $value)) {
            $fail("The {$attribute} field contains prohibited language.");
        }
    }
}

Display-time masking. For free-form content, store the original and mask on output so moderation decisions are reversible. Don't destroy the raw text at write time unless you have a separate audit path — you'll want it when you tune the dictionary.

Dictionary loading strategy. Keep the bulk of the words in a versioned file (or a dedicated banned_words table) and only the path/options in config/text-guard.php. This lets you update the dictionary without touching config cache state and lets non-developers contribute words via a deploy.

Bind once, reuse. The package's service provider typically registers a singleton; resolve through the facade or container rather than new Guard(...) inside loops, otherwise you rebuild the trie on every message. For batch jobs (importing old comments, scanning a table), resolve once outside the loop:

$guard = app(TextGuard::class);

UserComment::chunkById(500, function ($comments) use ($guard) {
    foreach ($comments as $comment) {
        if ($guard->check($comment->body)) {
            $comment->update(['needs_review' => true]);
        }
    }
});

Soft-flag before hard-block. A mature integration pattern: on first match, set a needs_review flag and let content through; escalate to hard rejection only for high-severity words. This keeps false positives from breaking checkout flows or support tickets.

Pair with a Laravel event. Fire a TextGuardMatched event on match so you can log, alert, or feed the offending content into a moderation queue, keeping the guard itself stateless.

Test with a fixture dictionary. In tests, override the config to a tiny inline list so assertions are deterministic and don't drift every time someone adds a word:

config(['text-guard.words' => ['badword']]);

Gotchas and Tips

Config caching silently freezes your word list. php artisan config:cache snapshots the inline words array; adding words to the config file without re-caching does nothing. Move the list to a dictionary file read at runtime, or add a deploy step that re-caches.

Obfuscation defeats naive matching. Spaces, punctuation, and zero-width characters between characters are the standard bypass. Normalize input before checking — strip whitespace and punctuation, fold full-width to half-width, lowercase — otherwise your dictionary will look fine in tests and fail in production.

English substrings cause the Scunthorpe problem. Substring matching is correct for CJK but wrong for Latin words. Keep short Latin words out of the list, or use word-boundary matching for ASCII entries, and maintain an explicit allowlist for known-safe strings (place names, surnames, product SKUs).

Multibyte and encoding. Ensure the input is valid UTF-8 before scanning and that the guard is using mb_* functions internally. Data coming from legacy imports, CSV uploads, or truncated columns can silently produce zero matches.

Trie memory vs. dictionary size. The singleton keeps the whole trie in memory per PHP process. A 100k-word list is fine; a multi-megabyte one multiplied across Octane workers or many FPM children is not. Measure before shipping a huge dictionary, and remember that under Octane a dictionary update requires a worker reload.

A match is not a verdict. Log the matched words (not just the boolean) so you can tune the list. Keep those logs private — dumping your dictionary into an exception message or API error response hands attackers the bypass map.

Laravel 13 note. 1.0.4 is the first release that allows Laravel 13. If your composer.json pins the framework or the package at 1.0.3, upgrade the package constraint alone; no application code changes are expected. Verify with a quick run of your moderation test suite against the new framework version, since that is the only variable that changed.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource