restruct / silverstripe-intelligent-404
Add additional smart redirection functionality to the existing 404 ErrorPage
Package info
github.com/restruct/silverstripe-intelligent-404
Type:silverstripe-vendormodule
pkg:composer/restruct/silverstripe-intelligent-404
Fund package maintenance!
Requires
- php: ^8.1
- silverstripe/cms: ^5 || ^6
- silverstripe/errorpage: ^2 || ^3
- silverstripe/framework: ^5 || ^6
Requires (Dev)
- silverstripe/recipe-testing: ^3 || ^4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-24 21:15:43 UTC
README
Maintained by Restruct. If this module saves you time, you can support ongoing maintenance.
Forked here because the original module is no longer actively maintained.
An extension to add additional functionality to the existing 404 ErrorPage. If a 404 page is called, it tries to guess the intended page by matching up the last segment of the url to all SiteTree pages. It also uses soundex to match similar sounding pages to find other alternatives.
Other DataObjects (eg: products) can be added too provided they contain a Link() function
Requirements
- Silverstripe 5 or 6, with
silverstripe/cmsandsilverstripe/errorpage - PHP 8.1 or newer (Silverstripe 6 itself requires 8.3)
Installation
composer require restruct/silverstripe-intelligent-404
The extension is applied to SilverStripe\ErrorPage\ErrorPageController automatically. Run a
dev/build (Silverstripe 5) or sake db:build (Silverstripe 6) with a flush afterwards.
Version compatibility
| Branch | Module version | Silverstripe | PHP |
|---|---|---|---|
main |
4.x (from 4.1.0) |
^5 || ^6 |
^8.1 |
| (tags only) | 4.0.0 |
^6 |
as Silverstripe 6 (^8.3) |
| (tags only) | 3.0.1, 3.0.2 |
^4 || ^5 |
as the Silverstripe version |
| (tags only) | 3.0.0 |
^5 |
as Silverstripe 5 (^8.1) |
Silverstripe 4 reached end of life in April 2025 and is no longer supported or tested here. Projects
still on it should stay on the 3.0.x tags, which remain available. Silverstripe 3 and 4 projects
using the original axllent/silverstripe-intelligent-404 package are unaffected by this fork.
main is the only maintained line: it supports every Silverstripe version this module still
targets, so there is no separate maintenance branch.
composer.json is the source of truth for exact constraints; this table is a quick reference.
How it works
If a 404 error is detected (note: does not work in dev mode by default):
- It will search SiteTree for all matching URLSegments, as well as any that sound the same (using PHP's soundex()).
- If 1 exact match is found, a 301 redirect is sent.
- Else if no exact match is found, and 1 similar page is found, a 301 redirect is sent to the similar page.
- Else if more than 1 exact or similar page is found, a regular 404 page is shown and the list of possible options is shown (ie: "Were you looking for one of the following pages?") directly beneath it.
- Else a regular 404 page is shown.
Only the last segment of the requested URL is matched, and a trailing .asp, .aspx, .htm,
.html, .php, .php3 or .php4 extension is ignored, so /old-site/about-us.php finds a page
with the URL segment about-us anywhere in the site tree. Only the 404 ErrorPage is affected;
other error pages are left alone.
Only what the visitor may see
A 404 never lists or redirects to a record the current visitor may not view: every match is checked
with canView() for the logged-in member (or for an anonymous visitor). A login-protected page is
therefore suggested to the members who can open it, and to nobody else. A match that fails the check
does not count towards the single-match redirect either, so it cannot win over a public soundalike.
Pages hidden from search (Show in search? unticked, ShowInSearch = 0) are left out as well, by
default. This applies to every record that has a ShowInSearch field, also when only a subclass of
the configured class declares it.
Configuration
By default Intelligent-404 will just try match 404 pages with all pages in your SiteTree, except for pages with the classnames:
SilverStripe\ErrorPage\ErrorPageSilverStripe\CMS\Model\RedirectorPageSilverStripe\CMS\Model\VirtualPage
This means that any page types you add should automatically be included, and can optionally be excluded as well.
Generic options
By default Intelligent-404 will not work in dev mode (to help you spot issues). You can turn this on by setting allow_in_dev_mode: true.
By default the module will also redirect if either 1 exact match or one potential match is found (eg: the page has been recreated elsewhere).
You can change this by setting redirect_on_single_match: false, in which case the single match is listed as an option instead.
Records hidden from search are not matched (see above). Set exclude_hidden_from_search: false to
match them again; canView() is still checked.
A data_objects entry whose own filter selects ShowInSearch: 0 matches nothing while this is on,
because both filters apply; set exclude_hidden_from_search: false for such a setup.
Restruct\Silverstripe\Intelligent404\Intelligent404: allow_in_dev_mode: true # allow this to work in dev mode (default false) redirect_on_single_match: false # do not auto-redirect if one exact match is found (default true) exclude_hidden_from_search: false # also match records with ShowInSearch = 0 (default true)
Adding other DataObjects
It is possible to add other DataObjects to the Intelligent-404 matching (such as a product database), provided that the DataObject has a Link() function. To add your own DataObjects, create a yml config (eg: app/_config/intelligent404.yml) with the following syntax:
Restruct\Silverstripe\Intelligent404\Intelligent404: data_objects: App\Model\Product: # fully qualified class name group: Products # optional group to include the results list into (default 'Pages') filter: # optionally filter which results to include (see below) Stock:GreaterThan: 0 exclude: # optional filter out results (see below) Expired: 1
The URL segment matched is the last segment of the object's Link().
The class needs a canView() that lets your visitors see it. Only records the visitor may view
are matched, and DataObject::canView() on its own allows administrators only. A class without its
own canView() is therefore never suggested to the public. For a public catalogue:
public function canView($member = null) { return true; }
Notes:
- Class names
Use the fully qualified class name, without a leading backslash (App\Model\Product). A leading
backslash (\App\Model\Product), which earlier versions of this README asked for, is still
accepted: from 4.1.0 it is stripped before the class is looked up. Before that, such an entry was
silently skipped unless the class happened to be loaded already.
Group (group:)
By default, all results will get passed on to your template as results of a $Pages ArrayList. You can optionally split your
suggestion results (for instance for different ordering or templating) by adding a group: <groupname>
as in the example above. Then in your template you would do a <% loop $Products %> in addition to your $Pages.
If you add your own grouping, please be sure to create your own templates/Intelligent404Options.ss to allow for those:
the bundled template renders $Pages only.
Filter results (filter:)
You are able to filter the database field matches, for instance (in the example) products that have a Stock greater than 0.
Please refer to the Searchfilters documentation for more information.
Exclude results (exclude:)
You can also exclude database field matches, for instance (in the example) products that have a Expired of 1.
Template variables
When options are shown (step 4 above), the extension sets these on the error page controller:
| Variable | Contents |
|---|---|
$Content |
the error page's content with the rendered options appended |
$ContentWithout404Options |
the error page's content as entered in the CMS, without the options |
$Intelligent404Options |
the rendered options on their own (templates/Intelligent404Options.ss) |
$SearchQuery |
the requested segment, sanitised for display or a search form: only letters, digits, -, _ and . are kept, and dashes and underscores become spaces |
To place the options yourself, render $ContentWithout404Options and $Intelligent404Options
instead of $Content in your ErrorPage template. Override Intelligent404Options.ss in your theme
to change their markup; the heading is translatable (Intelligent404.OptionsHeader).
Running the tests
The module cannot be tested on its own: it needs a host Silverstripe project with silverstripe/recipe-cms
and silverstripe/recipe-testing. Require it there through a Composer path repository with
symlink: true - /tests is export-ignore, so a dist or mirrored install contains no tests - add
its test namespace (Restruct\Silverstripe\Intelligent404\Tests\ ->
vendor/restruct/silverstripe-intelligent-404/tests/) to the host's autoload-dev, and copy this
module's phpunit.xml.dist to the host root as phpunit.xml (it is a template: its paths are relative
to the host root, so it does not work in place). Then:
# Silverstripe 5 (PHPUnit 9) - the path must come before flush=1 vendor/bin/phpunit vendor/restruct/silverstripe-intelligent-404/tests flush=1 --fail-on-empty-test-suite # Silverstripe 6 (PHPUnit 11) - flush through the env var: a flush=1 argument makes PHPUnit # abort (Test file "flush=1" not found) SS_PHPUNIT_FLUSH=1 vendor/bin/phpunit vendor/restruct/silverstripe-intelligent-404/tests --fail-on-empty-test-suite
CI runs the same suite against Silverstripe 5 and 6 on every push; see .github/workflows/ci.yml.