Official API Available! This package powers blasp.app - a universal profanity filtering REST API that works with any language. Free tier with 1,000 requests/month, multi-language support, and custom word lists.
Blasp is a powerful, extensible profanity filter for Laravel. Version 4 is a ground-up rewrite with a driver-based architecture, severity scoring, masking strategies, Eloquent model integration, and a clean fluent API.
- Driver Architecture β
regex(detects obfuscation, substitutions, separators),pattern(fast exact matching),phonetic(catches sound-alike evasions), orpipeline(chains multiple drivers together). Extend with custom drivers. - Multi-Language β English, Spanish, German, French with language-specific normalizers. Check one, many, or all at once.
- Severity Scoring β Words categorised as mild/moderate/high/extreme. Filter by minimum severity and get a 0-100 score.
- Masking Strategies β Character mask (
*,#), grawlix (!@#$%), or a custom callback. - Eloquent Integration β
Blaspabletrait auto-sanitizes or rejects profanity on model save. - Middleware β Reject or sanitize profane request fields with configurable severity.
- Validation Rules β Fluent validation rule with language, severity, and score threshold support.
- Testing Utilities β
Blasp::fake()for test doubles with assertions. - Events β
ProfanityDetected,ContentBlocked, andModelProfanityDetected.
- PHP 8.2+
- Laravel 8.0+
composer require blaspsoft/blaspPublish configuration:
# Everything (config + language files)
php artisan vendor:publish --tag="blasp"
# Config only
php artisan vendor:publish --tag="blasp-config"
# Language files only
php artisan vendor:publish --tag="blasp-languages"use Blaspsoft\Blasp\Facades\Blasp;
$result = Blasp::check('This is a fucking sentence');
$result->isOffensive(); // true
$result->clean(); // "This is a ******* sentence"
$result->original(); // "This is a fucking sentence"
$result->score(); // 30
$result->count(); // 1
$result->uniqueWords(); // ['fucking']
$result->severity(); // Severity::HighAll builder methods return a PendingCheck and can be chained:
// Language selection
Blasp::in('spanish')->check($text);
Blasp::in('english', 'french')->check($text);
Blasp::inAllLanguages()->check($text);
// Language shortcuts
Blasp::english()->check($text);
Blasp::spanish()->check($text);
Blasp::german()->check($text);
Blasp::french()->check($text);
// Driver selection
Blasp::driver('regex')->check($text); // Full obfuscation detection (default)
Blasp::driver('pattern')->check($text); // Fast exact matching
Blasp::driver('phonetic')->check($text); // Sound-alike detection (e.g. "phuck", "sheit")
Blasp::driver('pipeline')->check($text); // Chain multiple drivers (config-based)
// Ad-hoc pipeline β chain any drivers without config
Blasp::pipeline('regex', 'phonetic')->check($text);
Blasp::pipeline('pattern', 'phonetic')->in('english')->mask('#')->check($text);
// Shorthand modes
Blasp::strict()->check($text); // Forces regex driver
Blasp::lenient()->check($text); // Forces pattern driver
// Masking
Blasp::mask('*')->check($text); // Character mask (default)
Blasp::mask('#')->check($text); // Custom character
Blasp::mask('grawlix')->check($text); // !@#$% cycling
Blasp::mask(fn($word, $len) => '[CENSORED]')->check($text); // Callback
// Severity filtering
use Blaspsoft\Blasp\Enums\Severity;
Blasp::withSeverity(Severity::High)->check($text); // Ignores mild/moderate
// Allow/block lists (merged with config)
Blasp::allow('damn', 'hell')->check($text);
Blasp::block('customword')->check($text);
// Chain everything
Blasp::spanish()
->mask('#')
->withSeverity(Severity::Moderate)
->check($text);
// Batch checking
$results = Blasp::checkMany(['text one', 'text two']);The Result object is returned by every check() call:
| Method | Returns | Description |
|---|---|---|
isOffensive() |
bool |
Text contains profanity |
isClean() |
bool |
Text is clean |
clean() |
string |
Text with profanities masked |
original() |
string |
Original unmodified text |
score() |
int |
Severity score (0-100) |
count() |
int |
Total profanity matches |
uniqueWords() |
array |
Unique base words detected |
severity() |
?Severity |
Highest severity in matches |
words() |
Collection |
MatchedWord objects with position, length, severity |
toArray() |
array |
Full result as array |
toJson() |
string |
Full result as JSON |
Result implements JsonSerializable, Stringable (returns clean text), and Countable.
The regex driver detects obfuscated profanity:
| Type | Example | Detected As |
|---|---|---|
| Straight match | fucking |
fucking |
| Substitution | fΓck!ng, f4ck |
fucking, fuck |
| Separators | f-u-c-k-i-n-g, f@ck |
fucking, fuck |
| Doubled | ffuucckkiinngg |
fucking |
| Combination | f-uuck!ng |
fucking |
Separator limit: The regex driver allows up to 3 separator characters between each letter (e.g.,
f--u--c--k). This covers all realistic obfuscation patterns while keeping regex complexity low enough for PHP-FPM environments.
The pattern driver only detects straight word-boundary matches.
The phonetic driver uses metaphone() + Levenshtein distance to catch words that sound like profanity but are spelled differently:
| Type | Example | Detected As |
|---|---|---|
| Phonetic spelling | phuck |
fuck |
| Shortened form | fuk |
fuck |
| Sound-alike | sheit |
shit |
Configure sensitivity in config/blasp.php under drivers.phonetic. A curated false-positive list prevents common words like "fork", "duck", and "beach" from being flagged.
The pipeline driver chains multiple drivers together so a single check() call runs all of them. It uses union merge semantics β text is flagged if any driver finds a match.
// Config-based: set 'default' => 'pipeline' or use driver('pipeline')
Blasp::driver('pipeline')->check('phuck this sh1t');
// Ad-hoc: pick drivers on the fly (no config needed)
Blasp::pipeline('regex', 'phonetic')->check('phuck this sh1t');
Blasp::pipeline('regex', 'pattern', 'phonetic')->check($text);When multiple drivers detect the same word at the same position, duplicates are removed β only the longest match is kept. Masks are applied from the merged result, and the score is recalculated across all matches.
Configure the default sub-drivers in config/blasp.php:
'drivers' => [
'pipeline' => [
'drivers' => ['regex', 'phonetic'], // Drivers to chain
],
],The Blaspable trait automatically checks model attributes during save:
use Blaspsoft\Blasp\Blaspable;
class Comment extends Model
{
use Blaspable;
protected array $blaspable = ['body', 'title'];
}// Sanitize mode (default) β profanity is masked, model saves
$comment = Comment::create(['body' => 'This is fucking great']);
$comment->body; // "This is ******* great"
// Check what happened
$comment->hadProfanity(); // true
$comment->blaspResults(); // ['body' => Result, 'title' => Result]
$comment->blaspResult('body'); // Result instanceclass Comment extends Model
{
use Blaspable;
protected array $blaspable = ['body', 'title'];
protected string $blaspMode = 'reject'; // 'sanitize' (default) | 'reject'
protected string $blaspLanguage = 'spanish'; // null = config default
protected string $blaspMask = '#'; // null = config default
}In reject mode, saving a model with profanity throws ProfanityRejectedException and the model is not persisted:
use Blaspsoft\Blasp\Exceptions\ProfanityRejectedException;
try {
$comment = Comment::create(['body' => 'profane text']);
} catch (ProfanityRejectedException $e) {
$e->attribute; // 'body'
$e->result; // Result instance
$e->model; // The unsaved model
}Comment::withoutBlaspChecking(function () {
Comment::create(['body' => 'unchecked content']);
});A ModelProfanityDetected event fires whenever profanity is detected on a model attribute (both sanitize and reject modes):
use Blaspsoft\Blasp\Events\ModelProfanityDetected;
Event::listen(ModelProfanityDetected::class, function ($event) {
$event->model; // The model instance
$event->attribute; // Which attribute had profanity
$event->result; // Result instance
});Use CheckProfanity to filter incoming request fields. A blasp middleware alias is registered automatically:
// Using the short alias (recommended)
Route::post('/comment', CommentController::class)
->middleware('blasp');
// With parameters: action, severity
Route::post('/comment', CommentController::class)
->middleware('blasp:sanitize,mild');
// Or using the class directly
use Blaspsoft\Blasp\Middleware\CheckProfanity;
Route::post('/comment', CommentController::class)
->middleware(CheckProfanity::class);| Action | Behaviour |
|---|---|
reject (default) |
Returns 422 JSON with field errors |
sanitize |
Replaces profane fields in the request and continues |
Configure which fields to check in config/blasp.php:
'middleware' => [
'action' => 'reject',
'fields' => ['*'], // '*' = all fields
'except' => ['password', 'email', '_token'], // Always skipped
'severity' => 'mild',
],$request->validate([
'comment' => ['required', 'blasp_check'],
'bio' => ['required', 'blasp_check:spanish'],
]);use Blaspsoft\Blasp\Rules\Profanity;
use Blaspsoft\Blasp\Enums\Severity;
$request->validate([
'comment' => ['required', Profanity::in('english')],
'bio' => ['required', Profanity::severity(Severity::High)],
'tagline' => ['required', Profanity::maxScore(50)],
]);The @clean directive sanitizes and escapes text for safe display in views:
<p>@clean($comment->body)</p>
{{-- Equivalent to: {{ app('blasp')->check($comment->body)->clean() }} --}}Output is HTML-escaped via e() for XSS safety.
Blasp registers macros on Laravel's Str and Stringable classes:
use Illuminate\Support\Str;
// Static methods
Str::isProfane('fuck this'); // true
Str::isProfane('hello'); // false
Str::cleanProfanity('fuck this'); // '**** this'
Str::cleanProfanity('hello'); // 'hello'
// Fluent Stringable methods
Str::of('fuck this')->isProfane(); // true
Str::of('fuck this')->cleanProfanity(); // Stringable('**** this')
Str::of('hello')->cleanProfanity()->upper(); // 'HELLO' (chaining works)Full config/blasp.php reference:
return [
'default' => env('BLASP_DRIVER', 'regex'), // 'regex' | 'pattern' | 'phonetic' | 'pipeline'
'language' => env('BLASP_LANGUAGE', 'english'), // Default language
'mask' => '*', // Default mask character
'severity' => 'mild', // Minimum severity
'events' => false, // Fire ProfanityDetected events
'cache' => [
'enabled' => true,
'driver' => env('BLASP_CACHE_DRIVER'),
'ttl' => 86400,
'results' => true, // Cache check() results by content hash
],
'middleware' => [
'action' => 'reject',
'fields' => ['*'],
'except' => ['password', 'email', '_token'],
'severity' => 'mild',
],
'model' => [
'mode' => env('BLASP_MODEL_MODE', 'sanitize'), // 'sanitize' | 'reject'
],
'drivers' => [
'pipeline' => [
'drivers' => ['regex', 'phonetic'], // Sub-drivers to chain
],
'phonetic' => [
'phonemes' => 4, // metaphone code length (2-8)
'min_word_length' => 3, // skip short words
'max_distance_ratio' => 0.6, // levenshtein threshold (0.3-0.8)
'supported_languages' => ['english'], // metaphone is English-oriented
'false_positives' => ['fork', '...'], // never flag these words
],
],
'allow' => [], // Global allow-list
'block' => [], // Global block-list
'separators' => [...], // Characters treated as separators
'substitutions' => [...], // Character leet-speak mappings
'false_positives' => [...], // Words that should never be flagged
];Implement DriverInterface and register with the manager:
use Blaspsoft\Blasp\Core\Contracts\DriverInterface;
use Blaspsoft\Blasp\Core\Result;
use Blaspsoft\Blasp\Core\Dictionary;
use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;
class MyDriver implements DriverInterface
{
public function detect(string $text, Dictionary $dictionary, MaskStrategyInterface $mask, array $options = []): Result
{
// Your detection logic
}
}
// Register in a service provider
Blasp::extend('my-driver', fn($app) => new MyDriver());
// Use it
Blasp::driver('my-driver')->check($text);Blasp caches check() results by default. When the same text is checked with the same configuration (language, driver, severity, allow/block lists), the cached result is returned instantly.
// First call β runs full analysis, caches result
$result = Blasp::check('some text');
// Second call β returns cached result
$result = Blasp::check('some text');Configure caching in config/blasp.php:
'cache' => [
'enabled' => true, // Master switch for all caching
'driver' => env('BLASP_CACHE_DRIVER'), // null = default cache driver
'ttl' => 86400, // Cache lifetime in seconds
'results' => true, // Cache check() results (disable independently)
],Result caching is automatically bypassed when using a CallbackMask (closures can't be serialized). Clear both dictionary and result caches with:
php artisan blasp:clearOr programmatically:
Dictionary::clearCache();# Clear the profanity cache
php artisan blasp:clear
# Test text from the command line
php artisan blasp:test "some text to check" --lang=english --detail
# List available languages with word counts
php artisan blasp:languagesuse Blaspsoft\Blasp\Facades\Blasp;
use Blaspsoft\Blasp\Core\Result;
// Replace with a fake β all checks return clean by default
Blasp::fake();
// Pre-configure specific responses
Blasp::fake([
'bad text' => Result::withMatches(['fuck']),
'clean text' => Result::none('clean text'),
]);
$result = Blasp::check('bad text');
$result->isOffensive(); // true
// Assertions
Blasp::assertChecked();
Blasp::assertCheckedTimes(1);
Blasp::assertCheckedWith('bad text');Blasp::withoutFiltering(function () {
// All checks return clean results
});Enable global events with 'events' => true in config:
| Event | Fired When | Properties |
|---|---|---|
ProfanityDetected |
check() finds profanity |
result, originalText |
ContentBlocked |
Middleware detects profanity | result, request, field, action |
ModelProfanityDetected |
Blaspable trait detects profanity | model, attribute, result |
ModelProfanityDetected always fires (not gated by the events config).
| v3 | v4 |
|---|---|
Blaspsoft\Blasp\Facades\Blasp |
Blaspsoft\Blasp\Facades\Blasp (unchanged) |
Blaspsoft\Blasp\ServiceProvider |
Blaspsoft\Blasp\BlaspServiceProvider |
The Laravel auto-discovery handles provider/alias registration automatically. The facade namespace is the same as v3, so no import changes are needed for the facade.
| v3 Key | v4 Key | Notes |
|---|---|---|
default_language |
language |
default_language still works as alias |
mask_character |
mask |
mask_character still works as alias |
cache_driver |
cache.driver |
cache_driver still works as alias |
| β | default |
New: driver selection (regex/pattern) |
| β | severity |
New: minimum severity level |
| β | events |
New: enable global events |
| β | allow / block |
New: global allow/block lists |
| β | middleware |
New: middleware configuration section |
| β | model |
New: Blaspable trait configuration |
| v3 Method | v4 Method |
|---|---|
hasProfanity() |
isOffensive() |
getCleanString() |
clean() |
getSourceString() |
original() |
getProfanitiesCount() |
count() |
getUniqueProfanitiesFound() |
uniqueWords() |
All v3 methods still work as deprecated aliases.
| v3 Method | v4 Method |
|---|---|
maskWith($char) |
mask($char) |
allLanguages() |
inAllLanguages() |
language($lang) |
in($lang) |
configure($profanities, $falsePositives) |
block(...$words) / allow(...$words) |
All v3 methods still work as deprecated aliases.
- Driver architecture β
regexandpatterndrivers, custom driver support - Severity system β Mild/Moderate/High/Extreme levels with scoring
- Masking strategies β Grawlix and callback masking
- Blaspable trait β Automatic Eloquent model profanity checking
- Middleware β Request-level profanity filtering
- Fluent validation rule β
Profanity::in('spanish')->severity(Severity::High) - Testing utilities β
Blasp::fake(), assertions,withoutFiltering() - Events β
ProfanityDetected,ContentBlocked,ModelProfanityDetected - Artisan commands β
blasp:clear,blasp:test,blasp:languages - Batch checking β
Blasp::checkMany([...]) - Multi-language in one call β
Blasp::in('english', 'spanish')->check($text)
We welcome contributions! Please see our Contributing Guide for details.
See CHANGELOG.md for detailed version history.
Blasp is open-sourced software licensed under the MIT license.