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.

4.0.5(5mo ago)12041BSD-3-ClausePHPPHP ^8.3CI passing

Since Mar 13Pushed 5mo 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 1mo ago

READMEChangelogDependencies (6)Versions (37)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/tests.yml/badge.svg)](https://github.com/jonathanmaron/ctw-middleware-trailingslash/actions/workflows/tests.yml)[![Scrutinizer Build](https://camo.githubusercontent.com/99dfa982cf9ed44557b453125ee55fae417f71f72a3de71d87614c1568932659/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6a6f6e617468616e6d61726f6e2f6374772d6d6964646c65776172652d747261696c696e67736c6173682f6261646765732f6275696c642e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/jonathanmaron/ctw-middleware-trailingslash/build-status/master)[![Scrutinizer Quality](https://camo.githubusercontent.com/38ed104c2dcfdacb8f737e0575fbee5d79d19f70d9b35b2f41bebf35cad678f6/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6a6f6e617468616e6d61726f6e2f6374772d6d6964646c65776172652d747261696c696e67736c6173682f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/jonathanmaron/ctw-middleware-trailingslash/?branch=master)[![Code Coverage](https://camo.githubusercontent.com/d2cf1827c672154215101988fc0bf9b0d880531adf553e5d357be05545087af9/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6a6f6e617468616e6d61726f6e2f6374772d6d6964646c65776172652d747261696c696e67736c6173682f6261646765732f636f7665726167652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/jonathanmaron/ctw-middleware-trailingslash/?branch=master)

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

47

—

FairBetter than 94% of packages

Maintenance71

Regular maintenance activity

Popularity14

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity79

Established project with proven stability

 Bus Factor1

Top contributor holds 98.5% 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 ~49 days

Recently: every ~39 days

Total

36

Last Release

165d 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

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

3.0.0PHP ^8.0

3.0.9PHP ^8.1

4.0.0PHP ^8.3

### 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 (67 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

[nelmio/api-doc-bundle

Generates documentation for your REST API from attributes

2.3k63.6M232](/packages/nelmio-api-doc-bundle)[api-platform/core

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

2.6k48.1M235](/packages/api-platform-core)[api-platform/state

API Platform state interfaces

223.4M57](/packages/api-platform-state)[akrabat/ip-address-middleware

PSR-15 middleware that determines the client IP address and stores it as a ServerRequest attribute

1702.5M18](/packages/akrabat-ip-address-middleware)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28483.0k2](/packages/mezzio-mezzio-authentication-oauth2)[mezzio/mezzio-authentication

Authentication middleware for Mezzio and PSR-7 applications

121.6M26](/packages/mezzio-mezzio-authentication)

PHPackages © 2026

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