asignua/filament-redirects
Asignua Filament Redirects is a Filament plugin for managing URL redirects directly from your admin panel. Create and edit 301/302 redirects, handle incoming requests with middleware, and keep your Laravel site's links organized without leaving Filament.
redirects, plus the 404 log table). Publish the config so config/redirects.php is in your repo — that file is the first place to look: gone_status (410 by default), preserve_query, and the redirect/gone status codes live there.RedirectPath (the canonical normalize() / display() pair — everything you store goes through it) and RedirectRepository (the write-side API, including createFromLog()).php artisan redirects:recheck once manually to see how the package re-validates targets; the panel's "Recheck" button is the same operation, scoped to recent rows.old_path. Always funnel through the normalizer. The package treats the stored value as canonical and the form value as "display", and v1.0.2 tightened the invariant so display() is the exact inverse of normalize(). If you build an import command, call the repository/normalizer rather than mass-assigning raw strings.RedirectRepository::createFromLog() does not re-normalize, which is deliberate: log entries may contain https:/site.test/x or a language-prefixed en/foo. When you write your own admin action or Artisan command that consumes log rows, pass the log path through createFromLog() rather than normalize-then-create; double normalization was a real source of corrupt rows.saved/deleted listeners and the repository use afterCommit to flush the cache, so it is safe (and encouraged) to create redirects inside your own DB::transaction(). Cache invalidation now happens once the outermost transaction commits; a redirect created mid-transaction will not be live until then, which is normally what you want.Gone (served with gone_status, 410 by default) over deleting rows for retired content: it keeps the record, drops the stale binding, and returns the right status to crawlers.en row whose target is de/aktion is served as /de/aktion; the unprefixed language is reached by pasting the full address, which is kept whole and served from the root. Bulk "Create redirects" requires rows of a single language — chunk your import by language instead of mixing.preserve_query on, the incoming query is appended to the target — before any #fragment, since v1.0.2. If your targets already contain query strings, test the combination; appending to a target that already has a ? is the classic footgun.Location, not on database state. Round-trip a few nasty sources through the form/save path (my page, what?, %3F, https:/site.test/x) and assert the served Location header. That exercises normalize → display → normalize in the same way the edit form does.display() escapes scheme:/ on purpose. A source stored verbatim from the 404 log (https:/site.test/x, https:/evil.com/x) appears escaped in the edit form. That is not corruption — it is what makes the value survive a round-trip through the form. Do not "clean it up" in a custom view.RedirectPath::unchanged() guards this. If you build a custom edit form, replicate the "did the user actually change this field?" check, otherwise a prefixed source like en/foo gets normalized a second time and you will chase phantom redirect loops.%3F, %23 and %25 stay encoded, and an embedded query/fragment is left alone. If your redirect targets are built from user input, do not pre-decode them yourself; you will double-encode.old_path is capped at 191 characters, checked on the normalized path in both the form and the repository. Long sources now raise a ValidationException rather than a SQL error — catch it in importers and report the offending row instead of aborting the batch.afterCommit now; if you bypass the model (raw query builder inserts, DB::table('redirects')->insert()), you skip the listeners and leave a stale map. Route bulk imports through the repository or trigger a flush yourself.redirects:recheck still walks the entire log. Use the command in a scheduled job and keep the panel for spot checks; do not expect the button to fix years of backlog.gone_status, preserve_query, status codes), the model listeners for cache invalidation, and the repository's create methods. Prefer composing around RedirectRepository::createFromLog() over writing your own normalizer — the v1.0.2 fixes all landed in that boundary, and custom code at that seam is where regressions will appear.How can I help you explore Laravel packages today?