ivanbaric/sanigen
Sanigen is a Laravel package for defining and generating reusable sanitizers that clean and normalize incoming request data before validation or persistence. It helps keep form and API input consistent, predictable, and safe with a simple, rule-based workflow.
Sanigen 1.9.0 is a sanitization layer, not a validator — it cleans values before they hit your database, so it sits before validation and before casting. Get it wired once and it runs on every save without further ceremony.
composer require ivanbaric/sanigen
php artisan vendor:publish --tag=sanigen-config
Look at config/sanigen.php first. In 1.9 the important keys are the traversal limits (depth, item count, scalar length) — these are now enforced across a whole root traversal, so they're your primary safety valve for nested JSON. Also confirm the sanitizer alias list there; plain_text and safe_html are the two you'll use most.
Look second at the model-facing trait and the sanitizeAttributes() entry point. That method is the documented seam — if you need to sanitize something outside Eloquent, call it directly rather than reaching for internals.
First use case — a plain string attribute:
use IvanBaric\Sanigen\Concerns\SanitizesAttributes;
class Post extends Model
{
use SanitizesAttributes;
protected array $sanitizers = [
'title' => 'plain_text',
'body' => 'safe_html',
];
}
Save a Post with <script> in the body and confirm it's stripped on refresh(). If that works, the rest is configuration.
Rely on automatic recursion; don't hand-write levels. The headline 1.9 change is that root rules recurse on their own. A rule on meta now walks the entire array/object tree beneath it, preserving keys, structure, integers, floats, booleans, and nulls. Writing meta.address.line1 and meta.address.line2 separately is now redundant noise.
Use dot-notation and wildcards only for heterogeneous JSON. Where a subtree genuinely has different shapes per key, target them explicitly:
protected array $sanitizers = [
'meta' => 'plain_text',
'meta.*.url' => 'url_clean',
'meta.*.caption' => 'plain_text',
'blocks.*.html' => 'safe_html',
];
Most-specific path wins, and the compiled engine resolves this in a single pass with deterministic precedence — so ordering in the array doesn't change the outcome.
Spatie Translatable is a first-class path now. Swap the trait:
use IvanBaric\Sanigen\Concerns\HasSanitizedTranslations;
class Page extends Model
{
use HasSanitizedTranslations;
public array $translatable = ['title', 'body'];
}
Each locale string gets its own pipeline, so a bad de value can't affect en.
Sanitize at the write boundary, not in controllers. Attach rules to the model and let every save path — forms, API resources, seeders, imports, jobs — inherit them. One sanitizeAttributes() call per top-level root is guaranteed, so nested saves and saveQuietly() don't double-process.
Prefer aliases over bespoke regex. The built-in pipelines already handle Unicode NFC normalization and newline normalization that hand-rolled preg_replace calls will get wrong.
recursive: prefix. It still works in 1.9 but is deprecated and slated for removal in a major. Grep your rule strings for recursive: and flatten them now — behaviour is identical, so this is a safe mechanical change.strlen($model->meta['count']), that's your bug, not Sanigen's.blocks array with many items can consume the depth/item allowance and cause later siblings to be skipped. If large payloads are silently losing entries, raise the config limits deliberately rather than assuming a sanitizer is broken.plain_text mishandling Windows and old-Mac line endings, and same-name aliases like safe_html failing to delegate to their base sanitizer. Anything below 1.9 should be considered unsafe for currency fields.md5().assertSame against a hand-built array unless you control key order upstream — use assertEquals on the decoded structure.How can I help you explore Laravel packages today?