PHPackages                             vormkracht10/laravel-redirects - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. vormkracht10/laravel-redirects

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

vormkracht10/laravel-redirects
==============================

Add redirects to your Laravel app using a database, so you can dynamically manage it without writing code.

v4.0.1(2mo ago)2211[2 PRs](https://github.com/backstagephp/laravel-redirects/pulls)MITPHPPHP ^8.3CI passing

Since Mar 20Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/backstagephp/laravel-redirects)[ Packagist](https://packagist.org/packages/vormkracht10/laravel-redirects)[ Docs](https://github.com/backstagephp/laravel-redirects)[ GitHub Sponsors](https://github.com/backstagephp)[ RSS](/packages/vormkracht10-laravel-redirects/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (26)Versions (249)Used By (0)

Laravel Redirects
=================

[](#laravel-redirects)

[![Latest Version on Packagist](https://camo.githubusercontent.com/f049fc400158cb8058855ed7ec13dcc1ca9f39839b6ae29e233998ed504dae7c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6261636b73746167652f6c61726176656c2d7265646972656374732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/backstage/laravel-redirects)[![GitHub Tests Action Status](https://camo.githubusercontent.com/7ee0793036c834fdbd53e04dfbac8a4864c241af0dcab679a70f9b4abad0d390/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6261636b73746167657068702f6c61726176656c2d7265646972656374732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/backstagephp/laravel-redirects/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/ee5ce9e2eb2396b6d226d7b6f097956f83b2d6bbd76bbf24e885fab8502fe43a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6261636b73746167657068702f6c61726176656c2d7265646972656374732f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/backstagephp/laravel-redirects/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/777f866be130eb4b433c3042963dac10c467158aec7b6dbad3fbf23a81700066/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6261636b73746167652f6c61726176656c2d7265646972656374732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/backstage/laravel-redirects)

A powerful and flexible Laravel package for managing HTTP redirects through a database-driven approach. Dynamically create, update, and track redirects without modifying code or redeploying your application.

Features
--------

[](#features)

- **Database-driven redirects** - Manage redirects dynamically without code changes
- **Multiple matching strategies** - HTTP, wildcard, and strict matching middleware
- **Status code support** - 301, 302, 307, 308 redirects
- **Query string preservation** - Automatically maintains query parameters
- **Hit tracking** - Monitor redirect usage with built-in analytics
- **Event-driven automation** - Automatically create redirects when URLs change
- **SEO-friendly** - Proper HTTP status codes and trailing slash handling
- **Case sensitivity control** - Configure case-sensitive or insensitive matching
- **Trailing slash handling** - Flexible trailing slash sensitivity options
- **Protocol agnostic** - Works with HTTP and HTTPS seamlessly

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
    - [Creating Redirects](#creating-redirects)
    - [Automatic Redirects via Events](#automatic-redirects-via-events)
    - [Query String Handling](#query-string-handling)
    - [Tracking Redirect Hits](#tracking-redirect-hits)
- [Middleware](#middleware)
    - [HttpRedirects](#httpredirects)
    - [WildRedirects](#wildredirects)
    - [StrictRedirects](#strictredirects)
- [Advanced Usage](#advanced-usage)
    - [Custom Redirect Model](#custom-redirect-model)
    - [Managing Redirects Programmatically](#managing-redirects-programmatically)
- [API Reference](#api-reference)
- [Testing](#testing)
- [Changelog](#changelog)
- [Contributing](#contributing)
- [Security](#security)
- [Credits](#credits)
- [License](#license)

Installation
------------

[](#installation)

You can install the package via Composer:

```
composer require backstage/laravel-redirects
```

Publish and run the migrations:

```
php artisan vendor:publish --tag="laravel-redirects-migrations"
php artisan migrate
```

This will create a `redirects` table with the following structure:

ColumnTypeDescriptionulidULIDPrimary keysourcestringThe source URL to redirect fromdestinationstringThe destination URL to redirect tocodeintegerHTTP status code (301, 302, 307, 308)hitsintegerNumber of times this redirect was triggeredcreated\_attimestampWhen the redirect was createdupdated\_attimestampWhen the redirect was last modifiedOptionally, publish the configuration file:

```
php artisan vendor:publish --tag="laravel-redirects-config"
```

Configuration
-------------

[](#configuration)

The configuration file `config/redirects.php` provides extensive customization options:

```
return [
    /*
     * Available HTTP status codes for redirection.
     * Uncomment additional codes as needed.
     */
    'status_codes' => [
        301 => 'Moved Permanently',      // Permanent redirect, cached by browsers
        302 => 'Found',                  // Temporary redirect, not cached
        307 => 'Temporary Redirect',     // Temporary, maintains HTTP method
        308 => 'Permanent Redirect',     // Permanent, maintains HTTP method
    ],

    /*
     * The model to use for managing redirects.
     * Override this to use your own custom model.
     */
    'model' => Backstage\Redirects\Laravel\Models\Redirect::class,

    /*
     * Default status code for new redirects.
     * Can be overridden via REDIRECT_DEFAULT_STATUS_CODE env variable.
     */
    'default_status_code' => env('REDIRECT_DEFAULT_STATUS_CODE', 301),

    /*
     * Case sensitivity for URL matching.
     *
     * false: /example and /Example are treated as the same
     * true:  /example and /Example are treated as different URLs
     */
    'case_sensitive' => env('REDIRECT_CASE_SENSITIVE', false),

    /*
     * Trailing slash sensitivity for URL matching.
     *
     * false: /example and /example/ are treated as the same
     * true:  /example and /example/ are treated as different URLs
     */
    'trailing_slash_sensitive' => env('REDIRECT_TRAILING_SLASH_SENSITIVE', false),

    /*
     * Add trailing slash to redirect destinations.
     * Useful for maintaining URL consistency and SEO.
     */
    'trailing_slash' => env('REDIRECT_WITH_TRAILING_SLASH', false),

    /*
     * Middleware stack for handling redirects.
     * Order matters - first match wins.
     *
     * - HttpRedirects: Matches URLs with protocol/www variations
     * - WildRedirects: Partial URL matching (contains)
     * - StrictRedirects: Exact URL matching
     */
    'middleware' => [
        Backstage\Redirects\Laravel\Http\Middleware\HttpRedirects::class,
        Backstage\Redirects\Laravel\Http\Middleware\WildRedirects::class,
        Backstage\Redirects\Laravel\Http\Middleware\StrictRedirects::class,
    ],
];
```

### Environment Variables

[](#environment-variables)

Add these to your `.env` file for environment-specific configuration:

```
REDIRECT_DEFAULT_STATUS_CODE=301
REDIRECT_CASE_SENSITIVE=false
REDIRECT_TRAILING_SLASH_SENSITIVE=false
REDIRECT_WITH_TRAILING_SLASH=false
```

Usage
-----

[](#usage)

### Creating Redirects

[](#creating-redirects)

#### Database Seeder

[](#database-seeder)

```
use Backstage\Redirects\Laravel\Models\Redirect;

Redirect::create([
    'source' => '/old-page',
    'destination' => '/new-page',
    'code' => 301,
]);
```

#### Programmatically

[](#programmatically)

```
use Backstage\Redirects\Laravel\Models\Redirect;

// Permanent redirect (301)
Redirect::create([
    'source' => '/old-blog-post',
    'destination' => '/new-blog-post',
    'code' => 301,
]);

// Temporary redirect (302)
Redirect::create([
    'source' => '/maintenance',
    'destination' => '/under-construction',
    'code' => 302,
]);

// Wildcard redirect (matches any URL containing the source)
Redirect::create([
    'source' => '/blog/category/',
    'destination' => '/articles/',
    'code' => 301,
]);
```

#### Via Tinker

[](#via-tinker)

```
php artisan tinker
```

```
Redirect::create([
    'source' => 'example.com/old-url',
    'destination' => 'example.com/new-url',
    'code' => 301,
]);
```

### Automatic Redirects via Events

[](#automatic-redirects-via-events)

The package includes an event-listener system to automatically create redirects when URLs change. This is useful when updating slugs or moving content:

```
use Backstage\Redirects\Laravel\Events\UrlHasChanged;

// When a blog post URL changes
event(new UrlHasChanged(
    oldUrl: 'https://example.com/old-slug',
    newUrl: 'https://example.com/new-slug',
    code: 301
));
```

This automatically creates a redirect in the database:

```
// Created automatically by the listener
Redirect::create([
    'source' => 'https://example.com/old-slug',
    'destination' => 'https://example.com/new-slug',
    'code' => 301,
]);
```

**Integration Example with Eloquent Models:**

```
use Backstage\Redirects\Laravel\Events\UrlHasChanged;
use Illuminate\Database\Eloquent\Model;

class BlogPost extends Model
{
    protected static function booted()
    {
        static::updating(function ($post) {
            if ($post->isDirty('slug')) {
                $oldUrl = route('blog.show', $post->getOriginal('slug'));
                $newUrl = route('blog.show', $post->slug);

                event(new UrlHasChanged($oldUrl, $newUrl, 301));
            }
        });
    }
}
```

### Query String Handling

[](#query-string-handling)

The package automatically preserves query strings from the source URL and appends them to the destination:

```
Redirect::create([
    'source' => '/old-page',
    'destination' => '/new-page',
    'code' => 301,
]);
```

When a user visits:

```
/old-page?utm_source=email&utm_campaign=newsletter

```

They are redirected to:

```
/new-page?utm_source=email&utm_campaign=newsletter

```

If the destination already has query parameters:

```
Redirect::create([
    'source' => '/old-page',
    'destination' => '/new-page?foo=bar',
    'code' => 301,
]);
```

Visiting `/old-page?baz=qux` redirects to:

```
/new-page?foo=bar&baz=qux

```

### Tracking Redirect Hits

[](#tracking-redirect-hits)

Every time a redirect is triggered, the `hits` counter increments automatically:

```
$redirect = Redirect::where('source', '/old-page')->first();
echo $redirect->hits; // Number of times this redirect was used
```

Use this data for analytics and monitoring:

```
// Most used redirects
$popular = Redirect::orderBy('hits', 'desc')->take(10)->get();

// Recently created redirects
$recent = Redirect::latest()->take(10)->get();

// Unused redirects (candidates for removal)
$unused = Redirect::where('hits', 0)->get();
```

Middleware
----------

[](#middleware)

The package includes three middleware classes, each with different matching strategies. They run in the order defined in `config/redirects.php`.

### HttpRedirects

[](#httpredirects)

Matches URLs with protocol and `www` variations normalized:

```
Redirect::create([
    'source' => 'example.com/page',
    'destination' => 'example.com/new-page',
    'code' => 301,
]);
```

This matches all of these URLs:

- `http://example.com/page`
- `https://example.com/page`
- `http://www.example.com/page`
- `https://www.example.com/page`

### WildRedirects

[](#wildredirects)

Performs partial URL matching using `contains()`:

```
Redirect::create([
    'source' => '/blog/',
    'destination' => '/articles/',
    'code' => 301,
]);
```

This matches:

- `/blog/post-1` → `/articles/`
- `/blog/category/tech` → `/articles/`
- `/old-blog/archive` → `/articles/`

### StrictRedirects

[](#strictredirects)

Exact URL matching without query strings:

```
Redirect::create([
    'source' => 'example.com/exact-page',
    'destination' => 'example.com/new-exact-page',
    'code' => 301,
]);
```

This only matches:

- `http://example.com/exact-page`
- `https://example.com/exact-page`
- `http://www.example.com/exact-page`

But NOT:

- `example.com/exact-page/sub-page`
- `example.com/other-exact-page`

### Customizing Middleware Order

[](#customizing-middleware-order)

The middleware runs in the order defined in your config. First match wins:

```
'middleware' => [
    // 1. Try HTTP matching first (protocol/www normalized)
    Backstage\Redirects\Laravel\Http\Middleware\HttpRedirects::class,

    // 2. Then wildcard matching
    Backstage\Redirects\Laravel\Http\Middleware\WildRedirects::class,

    // 3. Finally exact matching
    Backstage\Redirects\Laravel\Http\Middleware\StrictRedirects::class,
],
```

You can reorder or remove middleware as needed. For example, to only use exact matching:

```
'middleware' => [
    Backstage\Redirects\Laravel\Http\Middleware\StrictRedirects::class,
],
```

Advanced Usage
--------------

[](#advanced-usage)

### Custom Redirect Model

[](#custom-redirect-model)

Create your own model extending the base Redirect model:

```
namespace App\Models;

use Backstage\Redirects\Laravel\Models\Redirect as BaseRedirect;

class Redirect extends BaseRedirect
{
    // Add custom scopes
    public function scopeActive($query)
    {
        return $query->where('active', true);
    }

    // Add relationships
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    // Override redirect logic
    public function redirect(Request $request): ?RedirectResponse
    {
        // Custom logic before redirect
        \Log::info("Redirecting from {$this->source} to {$this->destination}");

        return parent::redirect($request);
    }
}
```

Update your config:

```
'model' => App\Models\Redirect::class,
```

### Managing Redirects Programmatically

[](#managing-redirects-programmatically)

**Bulk Creation:**

```
$redirects = [
    ['source' => '/old-1', 'destination' => '/new-1', 'code' => 301],
    ['source' => '/old-2', 'destination' => '/new-2', 'code' => 301],
    ['source' => '/old-3', 'destination' => '/new-3', 'code' => 301],
];

foreach ($redirects as $redirect) {
    Redirect::create($redirect);
}
```

**Import from CSV:**

```
use Illuminate\Support\Facades\Storage;
use League\Csv\Reader;

$csv = Reader::createFromPath(Storage::path('redirects.csv'));
$csv->setHeaderOffset(0);

foreach ($csv->getRecords() as $record) {
    Redirect::create([
        'source' => $record['source'],
        'destination' => $record['destination'],
        'code' => $record['code'] ?? 301,
    ]);
}
```

**Conditional Redirects:**

```
// Only redirect if destination exists
if (Route::has('new-route')) {
    Redirect::create([
        'source' => '/old-route',
        'destination' => route('new-route'),
        'code' => 301,
    ]);
}
```

**Redirect Chains (avoid these):**

```
// BAD: Creates a redirect chain
// /page-1 → /page-2 → /page-3
Redirect::create(['source' => '/page-1', 'destination' => '/page-2', 'code' => 301]);
Redirect::create(['source' => '/page-2', 'destination' => '/page-3', 'code' => 301]);

// GOOD: Direct redirect
Redirect::create(['source' => '/page-1', 'destination' => '/page-3', 'code' => 301]);
```

API Reference
-------------

[](#api-reference)

### Redirect Model

[](#redirect-model)

**Properties:**

- `ulid` (string) - Primary key
- `source` (string) - Source URL
- `destination` (string) - Destination URL
- `code` (int) - HTTP status code
- `hits` (int) - Number of redirects performed
- `created_at` (timestamp)
- `updated_at` (timestamp)

**Methods:**

```
// Perform the redirect
public function redirect(Request $request): ?RedirectResponse

// Increment hits counter (called automatically)
public function increment('hits'): void
```

### Events

[](#events)

**UrlHasChanged Event:**

```
use Backstage\Redirects\Laravel\Events\UrlHasChanged;

event(new UrlHasChanged(
    oldUrl: 'https://example.com/old',
    newUrl: 'https://example.com/new',
    code: 301 // Optional, defaults to 301
));
```

**Properties:**

- `oldUrl` (string) - The old URL
- `newUrl` (string) - The new URL
- `code` (int) - HTTP status code (default: 301)

### Listeners

[](#listeners)

**RedirectOldUrlToNewUrl Listener:**

Automatically creates a redirect when `UrlHasChanged` event is dispatched.

HTTP Status Codes
-----------------

[](#http-status-codes)

Understanding when to use each status code:

CodeNameUse CaseCached by Browsers301Moved PermanentlyPermanent content relocation, old URL will never be used againYes302FoundTemporary redirect, old URL may be used againNo307Temporary RedirectTemporary redirect that preserves HTTP method (POST stays POST)No308Permanent RedirectPermanent redirect that preserves HTTP method (POST stays POST)Yes**Recommendations:**

- Use **301** for most permanent redirects (blog posts, pages, renamed resources)
- Use **302** for temporary situations (maintenance pages, A/B testing)
- Use **307** when redirecting form submissions temporarily
- Use **308** when permanently moving an API endpoint that receives POST/PUT/DELETE requests

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run tests with coverage:

```
composer test-coverage
```

Run static analysis:

```
composer analyse
```

Fix code style:

```
composer format
```

**Writing Tests:**

```
use Backstage\Redirects\Laravel\Models\Redirect;

it('redirects old URL to new URL', function () {
    Redirect::create([
        'source' => '/old',
        'destination' => '/new',
        'code' => 301,
    ]);

    $response = $this->get('/old');

    $response->assertRedirect('/new');
    $response->assertStatus(301);
});

it('preserves query strings', function () {
    Redirect::create([
        'source' => '/old',
        'destination' => '/new',
        'code' => 301,
    ]);

    $response = $this->get('/old?foo=bar');

    $response->assertRedirect('/new?foo=bar');
});

it('increments hits counter', function () {
    $redirect = Redirect::create([
        'source' => '/old',
        'destination' => '/new',
        'code' => 301,
    ]);

    expect($redirect->hits)->toBe(0);

    $this->get('/old');

    expect($redirect->fresh()->hits)->toBe(1);
});
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details on how to contribute to this package.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

**Security Considerations:**

- Validate redirect destinations to prevent open redirects
- Sanitize user input when creating redirects programmatically
- Monitor for redirect loops and chains
- Implement rate limiting to prevent abuse
- Use HTTPS for all redirect destinations when possible

Credits
-------

[](#credits)

- [Mark van Eijk](https://github.com/markvaneijk) - Creator and maintainer
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

---

Support
-------

[](#support)

- [Documentation](https://github.com/backstagephp/laravel-redirects)
- [Issue Tracker](https://github.com/backstagephp/laravel-redirects/issues)
- [Discussions](https://github.com/backstagephp/laravel-redirects/discussions)

Built with by [Backstage CMS](https://backstagephp.com)

###  Health Score

50

—

FairBetter than 96% of packages

Maintenance90

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 77.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

195

Last Release

60d ago

Major Versions

1.x-dev → v2.0.0-beta722026-01-20

v1.1.54 → v2.0.0-beta232026-01-21

v1.0 → v2.0.0-beta1062026-02-20

v2.0.11 → v4.0.12026-03-13

v2.0.29 → v4.0.02026-03-13

PHP version history (2 changes)v1.0.1PHP ^8.2

v1.1.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c6a425dc8645907a118a007438172d58c2016773f54ab3a834beff172632f13?d=identicon)[ux](/maintainers/ux)

---

Top Contributors

[![Casmo](https://avatars.githubusercontent.com/u/385764?v=4)](https://github.com/Casmo "Casmo (7 commits)")[![Baspa](https://avatars.githubusercontent.com/u/10845460?v=4)](https://github.com/Baspa "Baspa (1 commits)")[![markvaneijk](https://avatars.githubusercontent.com/u/1925388?v=4)](https://github.com/markvaneijk "markvaneijk (1 commits)")

---

Tags

phplaravelredirectsvormkracht10backstage

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/vormkracht10-laravel-redirects/health.svg)

```
[![Health](https://phpackages.com/badges/vormkracht10-laravel-redirects/health.svg)](https://phpackages.com/packages/vormkracht10-laravel-redirects)
```

###  Alternatives

[spatie/laravel-data

Create unified resources and data transfer objects

1.7k28.9M627](/packages/spatie-laravel-data)[worksome/exchange

Check Exchange Rates for any currency in Laravel.

123544.7k](/packages/worksome-exchange)[tapp/filament-google-autocomplete-field

Filament plugin that provides a Google Autocomplete field

3098.1k](/packages/tapp-filament-google-autocomplete-field)[ralphjsmit/livewire-urls

Get the previous and current url in Livewire.

82270.3k4](/packages/ralphjsmit-livewire-urls)[backstage/laravel-og-image

Laravel package to generate dynamic Open Graph images

723.2k](/packages/backstage-laravel-og-image)[hydrat/filament-table-layout-toggle

Filament plugin adding a toggle button to tables, allowing user to switch between Grid and Table layouts.

6292.3k1](/packages/hydrat-filament-table-layout-toggle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
