PHPackages                             ctw/ctw-middleware-trailingslash - 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. [HTTP &amp; Networking](/categories/http)
4. /
5. ctw/ctw-middleware-trailingslash

ActiveLibrary[HTTP &amp; Networking](/categories/http)

ctw/ctw-middleware-trailingslash
================================

This PSR-15 middleware adds a trailing slash to all URIs, redirecting with an HTTP 301 ("Moved Permanently") Location header.

5.0.2(3w ago)12441BSD-3-ClausePHPPHP ^8.5CI passing

Since Mar 13Pushed 3w ago1 watchersCompare

[ Source](https://github.com/jonathanmaron/ctw-middleware-trailingslash)[ Packagist](https://packagist.org/packages/ctw/ctw-middleware-trailingslash)[ RSS](/packages/ctw-ctw-middleware-trailingslash/feed)WikiDiscussions master Synced 2w ago

READMEChangelogDependencies (12)Versions (41)Used By (0)

Package "ctw/ctw-middleware-trailingslash"
==========================================

[](#package-ctwctw-middleware-trailingslash)

[![Latest Stable Version](https://camo.githubusercontent.com/68f9096d6ef76215da1492159223a7f24d54c1d9737419a351be8c0121b915cd/68747470733a2f2f706f7365722e707567782e6f72672f6374772f6374772d6d6964646c65776172652d747261696c696e67736c6173682f762f737461626c65)](https://packagist.org/packages/ctw/ctw-middleware-trailingslash)[![GitHub Actions](https://github.com/jonathanmaron/ctw-middleware-trailingslash/actions/workflows/ci.yml/badge.svg)](https://github.com/jonathanmaron/ctw-middleware-trailingslash/actions/workflows/ci.yml)

PSR-15 middleware that enforces trailing slashes on URLs by redirecting with HTTP 301, ensuring consistent URL canonicalization across your application.

Introduction
------------

[](#introduction)

### Why This Library Exists

[](#why-this-library-exists)

URLs with and without trailing slashes (e.g., `/about` vs `/about/`) are technically different resources. Without proper handling, the same content may be accessible at multiple URLs, causing SEO problems and potential caching issues.

This middleware enforces a consistent trailing slash convention by:

- **Automatic redirects**: Adds trailing slashes to URLs missing them
- **Permanent redirects**: Uses HTTP 301 to preserve SEO value
- **File exclusion**: Skips URLs with file extensions (e.g., `/style.css`)
- **Path configuration**: Exclude specific paths from processing (e.g., `/api/`)
- **Query preservation**: Maintains query strings during redirect

### Problems This Library Solves

[](#problems-this-library-solves)

1. **Duplicate content**: Search engines index both `/page` and `/page/` as separate pages
2. **Inconsistent linking**: Internal links may mix both formats
3. **Cache fragmentation**: CDNs may cache the same page under multiple URLs
4. **Relative URL issues**: Browser relative URL resolution differs based on trailing slash
5. **Manual redirects**: Maintaining nginx/Apache redirect rules is error-prone

### Where to Use This Library

[](#where-to-use-this-library)

- **SEO-focused websites**: Ensure canonical URLs for all pages
- **Content management systems**: Enforce URL consistency across dynamic content
- **Marketing sites**: Prevent duplicate content penalties
- **Multi-author platforms**: Normalize URLs regardless of how they're entered
- **API gateways**: Exclude API paths while normalizing web paths

### Design Goals

[](#design-goals)

1. **Permanent redirects**: Uses HTTP 301 for SEO benefit
2. **Smart file detection**: Skips URLs with file extensions automatically
3. **Configurable exclusions**: Disable for specific paths like `/api/`
4. **Early redirect**: Redirects before processing the request (no wasted computation)
5. **Query string preservation**: Full URI preserved during redirect

Requirements
------------

[](#requirements)

- PHP 8.3 or higher
- ctw/ctw-middleware ^4.0
- ctw/ctw-http ^4.0

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

[](#installation)

Install by adding the package as a [Composer](https://getcomposer.org) requirement:

```
composer require ctw/ctw-middleware-trailingslash
```

Usage Examples
--------------

[](#usage-examples)

### Basic Pipeline Registration (Mezzio)

[](#basic-pipeline-registration-mezzio)

```
use Ctw\Middleware\TrailingSlashMiddleware\TrailingSlashMiddleware;

// In config/pipeline.php - place early in the pipeline
$app->pipe(TrailingSlashMiddleware::class);
```

### ConfigProvider Registration

[](#configprovider-registration)

```
// config/config.php
return [
    // ...
    \Ctw\Middleware\TrailingSlashMiddleware\ConfigProvider::class,
];
```

### Redirect Behavior

[](#redirect-behavior)

Request URLResultStatus`/about`Redirect to `/about/`301`/about/`Pass through-`/page?id=1`Redirect to `/page/?id=1`301`/style.css`Pass through (has extension)-`/image.png`Pass through (has extension)-`/api/users`Pass through (if excluded)-`/`Pass through (root)-### Path Exclusions

[](#path-exclusions)

Exclude specific paths from trailing slash processing:

```
// config/autoload/trailingslash.global.php
return [
    'trailing_slash_middleware' => [
        'path_disable' => [
            '/api/',
            '/webhook/',
            '/.well-known/',
        ],
    ],
];
```

### File Extension Handling

[](#file-extension-handling)

The middleware automatically detects file extensions and skips processing:

```
// These URLs are NOT redirected:
/assets/style.css
/images/logo.png
/downloads/report.pdf
/scripts/app.js

// These URLs ARE redirected (no extension):
/about        → /about/
/products     → /products/
/contact      → /contact/
```

### Query String Preservation

[](#query-string-preservation)

Query strings are preserved during redirect:

```
/search?q=test → /search/?q=test
/page?id=1&sort=name → /page/?id=1&sort=name

```

### Response Header

[](#response-header)

```
HTTP/1.1 301 Moved Permanently
Location: https://example.com/about/
```

### Testing with cURL

[](#testing-with-curl)

```
# Check redirect behavior
curl -I https://example.com/about

# Expected response:
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/about/
```

### Nginx Equivalent

[](#nginx-equivalent)

This middleware replaces the need for nginx rewrite rules like:

```
# No longer needed with this middleware
rewrite ^([^.]*[^/])$ $1/ permanent;
```

### Why Trailing Slashes Matter

[](#why-trailing-slashes-matter)

1. **SEO**: Google treats `/page` and `/page/` as different URLs
2. **Relative links**: `` resolves differently based on trailing slash
3. **Caching**: CDNs may cache multiple versions of the same page
4. **Analytics**: Traffic may be split across URL variations

###  Health Score

55

—

FairBetter than 97% of packages

Maintenance95

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity83

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 98.9% 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 ~51 days

Recently: every ~60 days

Total

39

Last Release

25d ago

Major Versions

1.0.2 → 2.0.02022-02-14

2.0.0 → 3.0.02022-07-07

3.0.25 → 4.0.02024-06-18

4.0.5 → 5.0.02026-07-17

PHP version history (5 changes)1.0.0PHP ^7.4 || ^8.0

3.0.0PHP ^8.0

3.0.9PHP ^8.1

4.0.0PHP ^8.3

5.0.0PHP ^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/18d8bc9bdee8ab1b0cfccce6a06d2438acbed3a58b0046c4834c5678f6769342?d=identicon)[jonathanmaron](/maintainers/jonathanmaron)

---

Top Contributors

[![jonathanmaron](https://avatars.githubusercontent.com/u/298462?v=4)](https://github.com/jonathanmaron "jonathanmaron (86 commits)")[![evaKs13](https://avatars.githubusercontent.com/u/91725649?v=4)](https://github.com/evaKs13 "evaKs13 (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ctw-ctw-middleware-trailingslash/health.svg)

```
[![Health](https://phpackages.com/badges/ctw-ctw-middleware-trailingslash/health.svg)](https://phpackages.com/packages/ctw-ctw-middleware-trailingslash)
```

###  Alternatives

[api-platform/core

Build a fully-featured hypermedia or GraphQL API in minutes!

2.6k52.2M378](/packages/api-platform-core)[symfony/symfony

The Symfony PHP framework

31.4k87.4M2.2k](/packages/symfony-symfony)[api-platform/state

API Platform state interfaces

275.4M157](/packages/api-platform-state)[moonshine/moonshine

Laravel administration panel

1.3k268.2k89](/packages/moonshine-moonshine)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

568591.1k63](/packages/ecotone-ecotone)

PHPackages © 2026

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