Skip to content

URL Manager — Improvement & Growth Plan

Package: capell-app/url-manager · Kind: package · Tier: premium · Product group: Capell Search & SEO · Bundle: search-seo · Status: Draft

URL Manager is an action-driven redirect engine for Capell. It surfaces two admin Filament pages (RedirectRulesPage, NotFoundOpportunitiesPage) and decorates Core’s RedirectResolver binding on the public request path via UrlManagerRedirectResolver. Core domain logic lives in src/ActionsResolveRedirectRuleAction (exact/prefix/regex matching + hit recording), UpsertRedirectRuleAction, RecordNotFoundOpportunityAction, ConvertNotFoundOpportunityToRedirectAction, the CSV import/export family, BuildNotFoundRedirectSuggestionsAction, and ImportSeoSuiteBrokenLinksAction. Three models/tables back it: url_manager_redirect_rules, url_manager_redirect_hits, url_manager_not_found_opportunities. Deps: capell-app/core, capell-app/admin, lorisleiva/laravel-actions, spatie/laravel-data; soft-supports capell-app/seo-suite.

Current marketplace summary (verbatim): “Manage redirects, preserve moved URLs, track redirect hits, and turn repeated 404s or SEO Suite broken URL findings into redirect opportunities.” Current screenshot-quality follow-up demotes the 8 existing docs/screenshots/* PNGs from capell.json because visual audit showed the captures are dominated by a broken default shell/oversized black arc rather than usable Capell UI. docs/screenshots.json still defines the runner contract so the redirect workflows can be recaptured and re-promoted after the runner loads the correct assets.

  • Done/Shipped: Lowercase / canonicalise the matched path in normalisationNormalizeManagedUrlAction::handle() lowercases managed paths before hashing and resolution. Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php covers /Old-Page/ resolving from /old-page?utm_source=test. — src/Actions/NormalizeManagedUrlAction.php — S
  • Done/Shipped: Strip the query string before hashing for match (keep it only for preserve_query reattachment)NormalizeManagedUrlAction::pathFromUrl() returns only PHP_URL_PATH; query reattachment remains in UrlManagerRedirectResolver::targetUrl(). Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php and tests/Unit/Filament/RedirectRulesPageTest.php cover query-free matching and preserve-query response behavior. — src/Actions/NormalizeManagedUrlAction.php, consumed by ResolveRedirectRuleAction::findExactRule() — M
  • Done/Shipped: Make hot-path hit recording asynchronous / deferrableResolveRedirectRuleAction::recordHit() defers redirect hit writes to an application terminating() callback by default via capell-url-manager.hit_recording.defer, while keeping synchronous recording available for tests/explicit callers. RecordRedirectHitAction updates rule counters atomically when the deferred callback runs. Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php covers resolving a redirect without any immediate redirect_hits row or hit_count update, then recording after app()->terminate(). — src/Actions/ResolveRedirectRuleAction.php, src/Actions/RecordRedirectHitAction.php, config/capell-url-manager.php — M
  • Done/Shipped: avoid unbounded prefix/regex scans.findPrefixRule() builds concrete path candidates and resolves them with whereIn(...), priority, and longest-source ordering; findRegexRule() is priority ordered and capped by capell-url-manager.redirects.regex.max_rules_checked. — src/Actions/ResolveRedirectRuleAction.php, config/capell-url-manager.php — M
  • Done/Shipped: add a priority/ordering column to redirect_rules. — Redirect rules persist priority, the form exposes bounded priority editing, the table displays/sorts it, and resolver order honors priority for exact/prefix/regex paths. — database/migrations/2026_05_31_000001_create_url_manager_redirect_rules_table.php, database/migrations/2026_06_04_000001_add_priority_to_url_manager_redirect_rules_table.php, src/Actions/ResolveRedirectRuleAction.php, src/Filament/Pages/Schemas/RedirectRuleForm.php — M
  • Done/Shipped: form-level validation parity with the Action.RedirectRuleForm calls PrepareRedirectRuleDataAction from Filament validation closures so invalid regex, disallowed status codes, absolute target host rejection, self redirects, and loop checks surface before save. — src/Filament/Pages/Schemas/RedirectRuleForm.php, src/Actions/PrepareRedirectRuleDataAction.php — S
  • Done/Shipped: index redirect_hits.hit_at / rule FK for analytics queries. — Redirect hits now have a compound redirect_rule_id, hit_at index, and PruneRedirectHitsAction / capell:url-manager-prune-hits prune old hit rows using the configured retention window. — database/migrations/2026_05_31_000002_create_url_manager_redirect_hits_table.php, src/Actions/PruneRedirectHitsAction.php, src/Console/Commands/PruneRedirectHitsCommand.php — S

Mapped to capabilities[] in capell.json:

  • Done/Shipped: redirect loop & chain detection.PrepareRedirectRuleDataAction::resolveFinalTargetUrl() follows active exact-rule chains up to the configured max_chain_depth, rejects cycles, and collapses intermediate exact targets to their final target. — src/Actions/PrepareRedirectRuleDataAction.php, config/capell-url-manager.php
  • Done/Shipped: 404 capture is wired in-repo.UrlManagerServiceProvider appends RecordNotFoundOpportunityMiddleware to the frontend route middleware registry, and the middleware records non-ignored 404 responses as opportunities with site/language context. — src/Providers/UrlManagerServiceProvider.php, src/Http/Middleware/RecordNotFoundOpportunityMiddleware.php
  • Done/Shipped: Resolver auto-wiring verification. — Capell Core binds RedirectResolver::class to PageUrlRedirectResolver, Capell Frontend invokes resolve(RedirectResolver::class)->resolve(...) in ResolvePublicPageRequestAction before returning public pages, and URL Manager decorates the bound resolver when installed. Package coverage verifies the decorator preserves Core decisions and falls back to managed redirects. — ../capell-4/packages/core/src/Providers/CapellServiceProvider.php, ../capell-4/packages/frontend/src/Actions/ResolvePublicPageRequestAction.php, src/Providers/UrlManagerServiceProvider.php, tests/Unit/Actions/RedirectIntegrationActionsTest.php
  • Done/Shipped: auto slug-change redirects beyond exact. RecordChangedUrlRedirectAction creates the exact redirect for a changed page URL and, when create_prefix_redirect_on_parent_move is enabled, also records a prefix redirect for parent moves so child URLs continue to resolve. — src/Actions/RecordChangedUrlRedirectAction.php, config/capell-url-manager.php, tests/Unit/Actions/RedirectRuleActionsTest.php
  • Done/Shipped: canonical URL management policy. BuildCanonicalUrlAction provides the URL Manager canonical policy: force scheme/host when configured, lowercase paths, add/remove trailing slashes, and strip configured tracking query keys. The config exposes the policy and docs identify it as a callable integration surface rather than automatic tag rendering. — src/Actions/BuildCanonicalUrlAction.php, config/capell-url-manager.php, docs/overview.md, tests/Unit/Actions/RedirectRuleActionsTest.php
  • Done/Shipped: bulk operations in admin.RedirectRulesTable wires bulk activate, bulk disable, and bulk delete actions, plus import, preview-import, export, and template-download header actions. — src/Filament/Pages/Tables/RedirectRulesTable.php
  • 301 vs 302 guidance / defaults per source — status code is a free Select (301/302/307/308) with no guidance; auto-suggest 301 for permanent moves, 302 for temporary, and warn on 302 for slug-change redirects. — table-stakes polish
  • Done/Shipped: Open-redirect allowlist for absolute targetsPrepareRedirectRuleDataAction validates absolute target hosts against capell-url-manager.redirects.absolute_target_allowed_hosts plus the configured app.url host when enabled. Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php covers rejected, allowed, update, and import-preview paths.
  • Done/Shipped: Health check implementationUrlManagerHealthCheck now checks required tables, manifest-declared action classes, runtime/admin provider metadata, and required table metadata. Evidence: tests/Unit/Health/UrlManagerHealthCheckTest.php.
  • Done/Shipped: Health check is a stub — resolved by UrlManagerHealthCheck::runDiagnostics(), which returns table, action class, and provider metadata diagnostics and fails missing table/action/provider metadata cases. Evidence: tests/Unit/Health/UrlManagerHealthCheckTest.php. — src/Health/UrlManagerHealthCheck.php, capell.json healthChecks
  • Done/Shipped: Open-redirect surface — resolved by PrepareRedirectRuleDataAction::assertAllowedTargetUrl(), backed by configurable absolute target host allowlists and import/update coverage. Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php. — src/Actions/PrepareRedirectRuleDataAction.php, config/capell-url-manager.php
  • Done/Shipped: untrusted regex patterns are validated and bounded before save.PrepareRedirectRuleDataAction::normalizeRegexSource() trims regex sources, rejects empty, invalid, or overlong patterns using capell-url-manager.redirects.regex.max_pattern_length, and resolver checks are capped by max_rules_checked. — src/Actions/PrepareRedirectRuleDataAction.php, src/Actions/ResolveRedirectRuleAction.php, config/capell-url-manager.php
  • Done/Shipped: loop detection — see §3; save-time exact-rule chain traversal rejects cyclic redirect sets before they can be served to the public.
  • Done/Shipped: Synchronous double-write per redirect — resolved by deferring hit-row creation and rule counter updates to an application termination callback by default, so public redirect resolution can return the redirect decision before hit writes execute. Evidence: tests/Unit/Actions/RedirectRuleActionsTest.php covers no immediate hit row or counter update before app()->terminate().
  • No result caching of resolved rules — exact-match lookups by source_hash are cheap but prefix/regex resolution is O(rows) in PHP per request with no memoisation. Manifest adminQueryBudget: 40 covers admin, but there is no frontend query budget enforced.
  • Done/Shipped: Test coverage gaps reconciled. — Existing package coverage now proves fallback chaining/Core-decision precedence, managed resolver preserve-query reattachment, open-redirect rejection for create/update/import preview, loop/cycle rejection, SEO Suite broken-link import, health diagnostics, and manifest requirements. URL Manager has no public Blade output surface, so anonymous/non-admin output-safety coverage is not applicable for this package. — tests/Unit/Actions/RedirectIntegrationActionsTest.php, tests/Unit/Actions/RedirectRuleActionsTest.php, tests/Unit/Actions/NotFoundOpportunityActionsTest.php, tests/Unit/Filament/RedirectRulesPageTest.php, tests/Unit/Health/UrlManagerHealthCheckTest.php, tests/Unit/ManifestRequirementsTest.php
  • Done/Shipped: Action exception messages are translated. — URL normalization, redirect validation, import preview/import failures, CSV parsing, and 404 opportunity conversion now raise package translation strings via capell-url-manager::validation.*. — src/Actions/PrepareRedirectRuleDataAction.php, src/Actions/ImportRedirectRulesAction.php, src/Actions/PreviewRedirectRulesImportAction.php, src/Actions/ConvertNotFoundOpportunityToRedirectAction.php, src/Actions/ParseRedirectRulesCsvAction.php, resources/lang/en/validation.php
  • Done/Shipped: config file added.config/capell-url-manager.php now exposes status-code allowlist, absolute target hosts, redirect chain depth, regex bounds, prefix redirect behavior, deferred hit recording, hit retention, 404 capture settings, ignored 404 prefixes, and canonical URL behavior; the service provider publishes it via hasConfigFile('capell-url-manager').

Critique. The manifest summary is accurate but reads as a feature list, not a value statement, and buries the strongest hook (recovering lost SEO traffic). The package manifest now uses recovery-led marketplace copy, but the previously promoted 8 screenshots are demoted until they are recaptured as styled Capell UI instead of broken default-shell output.

Improved 1-sentence summary: Stop losing traffic to broken links — manage redirects, auto-preserve moved page URLs, and turn repeated 404s into recovered SEO.

Improved 3–4 sentence description: URL Manager keeps your site’s link equity intact when pages move or get renamed. It auto-creates redirects when a page URL changes, resolves exact, prefix, and regex rules on the live request, and tracks hit counts so you can see which redirects matter. Repeated 404s and SEO Suite broken-link findings surface as one-click redirect opportunities, with CSV import/export for bulk migrations. Built for editors and SEO teams who need redirect hygiene without touching server config.

Screenshot/media gaps: Reopened. The existing docs/screenshots/* set (create/edit form, import workflow, export workflow, health snapshot) and matching Capell runner contract remain useful as target definitions, but the committed captures are not buyer-facing quality and are no longer promoted in capell.json. Recapture styled light/dark redirect workflows before marking marketplace media complete. Still missing: the 404 Opportunities table with a convert action, and a redirect-hit analytics view (once §2 analytics lands).

Pricing / tier / bundle positioning: premium tier inside the search-seo bundle is right. Cross-sell is the lever: it supports seo-suite and consumes its BrokenLink rows, so position as the remediation half of an SEO loop (SEO Suite finds, URL Manager fixes). Strong fit with the migration-assistant and wordpress-importer Extension Suites — bulk redirect import is exactly what platform migrations need; surface ImportRedirectRulesAction as their redirect-mapping target. Bundle it as the default redirect layer whenever migration-assistant is purchased.

Differentiators / value props / target buyer: differentiator is the closed loop — automatic slug-change capture + 404 mining + SEO Suite import feeding suggested redirects, all inside the CMS. Target buyer: SEO managers and content teams on multi-site Capell installs who run frequent content reorganisations and migrations.

Keywords/tags: redirects, 301 redirect, 404 management, url management, seo, link equity, slug change, redirect import, broken links, site migration, regex redirect, multi-site.

ItemBucketEffortImpactSection ref
Done/Shipped: Implement real UrlManagerHealthCheck (tables/actions/provider discoverable)DoneSHigh§4
Done/Shipped 2026-06-08: Add managed 410 Gone rules for bulk/imported URL removalsDoneMHigh§3 — empty-target imports, form validation, resolver filtering, and frontend middleware
Recapture styled marketplace screenshots from the existing docs/screenshots/* runner contractLaterSHigh§5 — deferred until styled runner recapture; no implementation blocker
Done/Shipped: Strip query from match key; lowercase normalisationDoneMHigh§2
Done/Shipped: Open-redirect host allowlist for absolute targetsDoneMHigh§3, §4
Done/Shipped: Defer hot-path hit recording (queue / terminating)DoneMHigh§2, §4
Done/Shipped: Validate & bound regex patterns at write time (ReDoS)DoneMHigh§4
Done/Shipped: Redirect loop & chain detection on saveDoneMHigh§3
Done/Shipped: Wire 404 capture into frontend route middlewareDoneMHigh§3
Done/Shipped: Verify/confirm Core invokes the bound RedirectResolverDoneSHigh§3, §4
Done/Shipped: Add priority ordering column + resolver/form supportDoneMMed§2, §3
Done/Shipped: Replace unbounded prefix/regex scans with bounded candidate resolutionDoneMMed§2, §4
Done/Shipped: Tests: resolver fallback, preserve_query, open-redirect, loops, SEO importDoneMHigh§4
Done/Shipped: Add config/ for status codes, host allowlist, regex/retentionDoneSMed§4
Done/Shipped: Prefix-cascade auto-redirects on page-subtree movesDoneMMed§3
Done/Shipped: Canonical URL management surfaceDoneLMed§3
Done/Shipped: Translate Action exception messagesDoneSLow§4