Search by

restruct / silverstripe-login-branding

micschk

Admin login form (de-)branding module

Package info

github.com/restruct/silverstripe-login-branding

Type:silverstripe-vendormodule

pkg:composer/restruct/silverstripe-login-branding

Fund package maintenance!

restruct

Statistics

Installs: 920

Dependents: 0

Suggesters: 2

Stars: 0

Open Issues: 3

2.1.0 2026-09-30 11:27 UTC

This package is auto-updated.

Last update: 2026-09-30 11:27:43 UTC


README

Maintained by Restruct. If this module saves you time, you can support ongoing maintenance.

Reduces the Silverstripe branding of the login forms module and makes it easily configurable. Optionally, it also makes LeftAndMain.application_name the admin's site name instead of the editable SiteConfig.Title (see SiteConfig Title Override).

Requirements

  • Silverstripe 5 or 6, with silverstripe/login-forms
  • PHP 8.1 or newer
  • silverstripe/siteconfig for the SiteConfig title override (optional; the extension is only applied when it is installed)

Installation

composer require restruct/silverstripe-login-branding

The branding applies to the login forms as soon as the module is installed; everything below is optional configuration.

Version compatibility

Branch Module version Silverstripe PHP
main 2.x ^5 || ^6 ^8.1
(tags only) 0.1.2 - 1.1.x ^4 || ^5 || ^6 not declared
(tags only) 0.1.1 ^4 || ^5 not declared
(tags only) 0.1 ^4 not declared

Silverstripe 4 reached end of life in April 2025 and is no longer supported or tested here. Projects still on it can stay on the 1.x or 0.1.x tags, which remain available. On Silverstripe 6, use 2.x: on the earlier tags application_name_overrides_title does nothing there, and LoginIconTemplateAvailable() fatals when called directly before any template has rendered (the login page itself is not affected). Both are fixed in 2.x (see CHANGELOG.md).

main is the only maintained line: it supports every Silverstripe version this module still targets, so there is no separate maintenance branch. A version branch will be created only when a change cannot be made compatible across the supported range.

composer.json is the source of truth for exact constraints; this table is a quick reference.

Login form branding

Reduces the branding of the login forms like this:

Screenshot 2025-10-01 at 09 36 01

The branding above- and credits below the form are configurable via Yaml, options:

Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension:
  include_icon: true # default: true
  app_brand: 'Cycle App' # default: null (= just an icon/logo)
  built_by: 'Built by <a href="...">CoolCompany™</a>' # default: unconfigured-warning
  powered_by: 'Powered by <a href="https://silverstripe.org" target="_blank">Silverstripe</a>' # = default
Option Default Effect
include_icon true Show the icon (a project LoginIcon.ss, or the built-in shield-lock) above the form
app_brand null Text shown next to the icon; unset shows the icon only
built_by a <code> hint telling you to set it HTML credit line below the form; set to null to hide it
powered_by Powered by <a href="https://silverstripe.org">Silverstripe</a> HTML second credit line; set to null to hide it
use_app_brand_template false Render a project AppBrand.ss instead of icon + app_brand (legacy, see below)

built_by and powered_by are output as HTML, unescaped - put only trusted, developer-written markup in them.

Custom icon/logo/branding

Screenshot 2025-10-01 at 09 43 48

Place a LoginIcon.ss template somewhere in [app]/templates/[Includes/] or themes/[client-theme]/templates/[Includes/], eg:

<!-- LoginIcon.ss - bike icon -->
<svg width="16" height="16" fill="var(--color-link-primary)" class="bi bi-bicycle" viewBox="0 0 16 16">
    <path d="M4 4.5a.5.5 0 0 1 .5-.5H6a.5.5 0 0 1 0 1v.5h4.14l.386-1.158A.5.5 0 0 1 11 4h1a.5.5 0 0 1 0 
    1h-.64l-.311.935.807 1.29a3 3 0 1 1-.848.53l-.508-.812-2.076 3.322A.5.5 0 0 1 8 10.5H5.959a3 3 0 1 
    1-1.815-3.274L5 5.856V5h-.5a.5.5 0 0 1-.5-.5m1.5 2.443-.508.814c.5.444.85 1.054.967 1.743h1.139zM8 
    9.057 9.598 6.5H6.402zM4.937 9.5a2 2 0 0 0-.487-.877l-.548.877zM3.603 8.092A2 2 0 1 0 4.937 
    10.5H3a.5.5 0 0 1-.424-.765zm7.947.53a2 2 0 1 0 .848-.53l1.026 1.643a.5.5 0 1 1-.848.53z"/>
</svg>
<!-- Optionally add some inline <style> while at it... -->

And configure:

# config.yml
Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension:
  app_brand: 'Cycle App' # leave empty/unconfigured to show just the SVG (eg client's company logo)
  built_by: 'Built by <a href="https://restruct.nl" target="_blank">Restruct web & apps</a>'

(Legacy:) Replace header using custom AppBrand.ss template

Configure extension to use AppBrand.ss template instead:

Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension:
  use_app_brand_template: true

Add an AppBrand.ss template file somewhere, eg [app]/templates/[Includes/] or themes/[client-theme]/templates/[Includes/]:

<a class="login-icon" href="$AbsoluteBaseURL">
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-lock" viewBox="0 0 16 16">
        <path d="M5.338 1.59a61.44 61.44 0 0 0-2.837.856.481.481 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.725 10.725 0 0 0 2.287 2.233c.346.244.652.42.893.533.12.057.218.095.293.118a.55.55 0 0 0 .101.025.615.615 0 0 0 .1-.025c.076-.023.174-.061.294-.118.24-.113.547-.29.893-.533a10.726 10.726 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.775 11.775 0 0 1-2.517 2.453 7.159 7.159 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7.158 7.158 0 0 1-1.048-.625 11.777 11.777 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 62.456 62.456 0 0 1 5.072.56z"/>
        <path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415z"/>
    </svg>
    <h1 class="app-brand__logo">AppBrand</h1>
</a>

And set the theme as highest prio on SilverStripe\LoginForms\EnablerExtension (before #admin-branding):

---
Before:
  - '#admin-branding'
---
SilverStripe\LoginForms\EnablerExtension:
  login_themes:
    - 'client-theme'

Optionally set Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension.include_icon to false to remove the icon.

Shield-lock + bicycle icons kindly provided by Bootstrap Icons.

Expired login page notice

A login page left open longer than the session lives carries a security token (SecurityID) that the session no longer knows. Submitting it fails with "Your session has expired. Please re-submit the form." and the credentials have to be typed again. The module warns before that happens: once the page's token no longer matches the session, a notice appears above the form:

This page has expired. Refresh the page to continue.

The second sentence is a link that reloads the page.

The page asks the server when the tab becomes visible, regains focus, or is restored from the browser's back/forward cache (the pageshow event): the moments a stale page is about to be used. It does not count down to an expiry time, because there usually is none to count to: with the default Session.timeout of 0 the session ends whenever PHP's session garbage collection removes it. Once the notice is shown, checking stops. Network errors are ignored; the next check simply tries again.

It applies to every Security page with a form carrying a SecurityID, such as the login and lost password forms. The MFA steps (silverstripe/mfa) render no such form, so the notice never appears there; MFA handles an expired token itself.

Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension:
  expired_notice: true # default: true
  expired_notice_interval: 0 # default: 0 (seconds; 0 = no periodic check)
Option Default Effect
expired_notice true Load the notice script on Security pages. false also closes the check endpoint (404)
expired_notice_interval 0 Seconds between extra checks while the page is visible; 0 keeps only the checks on visibility, focus and back/forward navigation. Ignored while LeftAndMain.session_keepalive_ping is false

Each check touches the session. The request carries the session cookie, so PHP resumes the session, which resets its garbage-collection clock - exactly like the CMS keep-alive ping (Security/ping). That is why there is no periodic check by default: polling every few minutes would keep the session alive for as long as the tab stays open and visible, and so defeat the site's idle timeout, also on pages such as Security/changepassword. The checks on visibility and focus only happen when the user comes back to the page, which would touch the session anyway. If you do set an interval, it is dropped while LeftAndMain.session_keepalive_ping is false: a site that switched off the CMS keep-alive should not get a new one from here.

The check is a POST to Security/checktoken with the page's token. It answers only {"valid":true} or {"valid":false} (with Cache-Control: no-store). It never creates or changes a token, and never starts a session for a visitor who has none; for a visitor who has one, it resumes it as described above. The texts are translatable (en and nl are included), under Restruct\SilverStripe\AdminBranding\SecurityBrandingExtension .EXPIRED_NOTICE and .EXPIRED_NOTICE_REFRESH. The notice is styled from the login-forms theme's own colour variables, so it follows its dark mode as well.

The script and stylesheet are exposed from client/ through silverstripe/vendor-plugin; after updating, run composer vendor-expose if your deployment does not already do so.

SiteConfig Title Override

By default, the admin panel shows SiteConfig.Title (editable under Settings) in the left nav and browser tab. If you set LeftAndMain.application_name in config, it gets ignored when SiteConfig is installed.

This module can make application_name the authoritative source, overriding SiteConfig.Title in-memory and optionally removing the now-redundant fields from Settings. The override applies whenever a SiteConfig record is loaded, and is never written back: the stored title stays as it was, also when the record is saved with forceChange()->write(), which marks every field as changed. It applies to every SiteConfig record loaded from the database, not only the one returned by SiteConfig::current_site_config(): any code that reads SiteConfig.Title from a loaded record, including a DataList or get_by_id() of SiteConfig, gets application_name while the override is on.

# Set the application name
SilverStripe\Admin\LeftAndMain:
  application_name: 'My App'

# Enable the override
SilverStripe\SiteConfig\SiteConfig:
  application_name_overrides_title: true
  application_name_clear_fields: 'tab'  # see options below

application_name_clear_fields options

Value Behavior
false Override title but leave Title/Tagline fields in Settings
true (default) Remove Title + Tagline fields from Settings
'tab' Remove fields + remove the empty Main tab (if other tabs remain)

With false, a title typed in Settings is saved, but it is not shown while the override is on: $SiteConfig.Title keeps returning application_name.

hide_cms_page_permissions options

Removes the page-permission fields (who can view / edit / create top-level pages) from Settings, which mean nothing on a site without pages.

Value Behavior
'auto' (default) Remove them only when silverstripe/cms is not installed
true Always remove them
false Always leave them

The Access tab is removed as well when nothing else is left on it.

SilverStripe\SiteConfig\SiteConfig:
  hide_cms_page_permissions: true

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 Restruct\LoginBranding\Tests\ pointing at the module's tests/ to the host's autoload-dev, copy phpunit.xml.dist to the host root as phpunit.xml, then:

# Silverstripe 5 (PHPUnit 9) - the path must come before flush=1
vendor/bin/phpunit vendor/restruct/silverstripe-login-branding/tests flush=1

# Silverstripe 6 (PHPUnit 11) - a flush=1 argument is ignored, use the env var
SS_PHPUNIT_FLUSH=1 vendor/bin/phpunit --testsuite loginbranding

CI runs the same suite against Silverstripe 5 and 6 on every push; see .github/workflows/ci.yml.