Domyślny ekran wp-login.php jest rozpoznawalny w trzy sekundy: szare tło, logo WordPressa, generyczny formularz. Dla bloga hobbystycznego to akceptowalne. Dla sklepu, portalu klienta albo wdrożenia agencyjnego pod marką klienta to przeciek wizerunkowy. Redaktor loguje się codziennie; pierwsze wrażenie to nie strona główna, tylko ten ekran.
Ten poradnik pokazuje, jak zbudować white-label logowania wyłącznie hookami WordPressa: login_enqueue_scripts, filtry logo (login_headerurl, login_headertext), login_message, utwardzenie komunikatów błędów oraz decyzję mu-plugin kontra motyw. Bez wtyczki „Custom Login”, bez edycji plików rdzenia, bez CSS, które „ukrywa” URL logowania i udaje bezpieczeństwo.
Materiał jest pod polskie wdrożenia produkcyjne: motyw potomny albo must-use plugin, staging z osobnym kontem testowym, checklista regresji po aktualizacji WordPressa. Kod poniżej używa prefiksu wppoland_ (wppoland.com), żeby nie kolidował z innymi snippetami w tym samym projekcie.
Co WordPress ładuje na wp-login.php
Zanim podmienisz logo, warto wiedzieć, co rdzeń robi przy żądaniu wp-login.php:
- Ładuje
wp-admin/css/login.cssi zależne style kokpitu (przyciski, formularze). - Uruchamia akcję
login_enqueue_scripts- tu dokładasz własne CSS i JS, dokładnie jakwp_enqueue_scriptsna froncie. - Renderuje nagłówek z linkiem logo (domyślnie na wordpress.org), formularz, linki „Nie pamiętasz hasła?” / „Zarejestruj się”, stopkę.
- Przy błędzie walidacji uruchamia
wp_shake_jsi pokazuje komunikat z filtralogin_errors. - Filtry
login_messageilogin_messagespozwalają wstawić HTML nad formularzem (komunikat po wylogowaniu, info o utrzymaniu, banner SSO).
Kluczowa konsekwencja: strona logowania nie przechodzi przez pełny pipeline motywu frontowego. header.php, bloki FSE i większość enqueue z wp_enqueue_scripts tu nie działają. Stąd osobna akcja login_enqueue_scripts i osobny plik CSS. Jeśli wrzucisz style tylko do style.css motywu, na wp-login.php ich nie będzie.
Druga konsekwencja: aktualizacja WordPressa może zmienić selektory, kolejność skryptów albo markup formularza (np. dodatkowe pola przy 2FA). Branding oparty na hookach przeżywa aktualizację; patchowanie wp-login.php w rdzeniu - nie.
login_enqueue_scripts: CSS i assety
Oficjalny punkt zaczepienia to akcja login_enqueue_scripts. Używaj wp_enqueue_style / wp_enqueue_script, nie wklejaj kilkuset linii CSS w echo wewnątrz login_head, chyba że to naprawdę pięć reguł na logo.
Minimalny enqueue z motywu potomnego:
/**
* Ładuje style tylko na ekranie logowania.
*/
function wppoland_login_stylesheet() {
wp_enqueue_style(
'wppoland-login',
get_stylesheet_directory_uri() . '/assets/css/style-login.css',
array(),
wp_get_theme()->get( 'Version' )
);
}
add_action( 'login_enqueue_scripts', 'wppoland_login_stylesheet' );Dlaczego wersja z wp_get_theme()->get( 'Version' ): po deployu CSS nie zostaje w cache przeglądarki pod starym query stringiem. Na mu-pluginie użyj stałej wersji albo filemtime() ścieżki absolutnej.
Przykład style-login.css pod ciemny branding (dostosuj kolory do palety klienta, nie kopiuj ślepo):
body.login {
background-color: #0d1117;
background-image: none;
}
.login form {
background: #161b22;
border: 1px solid #30363d;
box-shadow: none;
border-radius: 8px;
}
.login label,
.login #nav a,
.login #backtoblog a {
color: #c9d1d9;
}
.wp-core-ui .button-primary {
background: #238636;
border-color: #238636;
text-shadow: none;
box-shadow: none;
}
.wp-core-ui .button-primary:hover,
.wp-core-ui .button-primary:focus {
background: #2ea043;
border-color: #2ea043;
}
.login #login_error,
.login .message,
.login .success {
border-left-width: 4px;
}Reguły praktyczne przy CSS logowania:
- Celuj w klasy, które WordPress faktycznie emituje:
body.login,#login,.login form,.wp-core-ui .button-primary,#login_error,.message. - Nie chowaj
#navi#backtoblogbez uzgodnienia z klientem - redaktorzy tracą link powrotu na front i odzyskiwanie hasła wygląda „zepsute”. - Unikaj
display: nonena polach formularza „na wszelki wypadek”. Wtyczka 2FA albo passkey doda własne kontrolki; ukrycie ich CSS-em wygląda jak awaria logowania. - Testuj kontrast (WCAG) na komunikatach błędów i etykietach - ciemne tło + szary tekst często odpada w audycie dostępności.
Jeśli potrzebujesz fontów klienta, enqueue je tutaj albo hostuj lokalnie w motywie. Trzecia domena na ekranie logowania to zbędny request i kolejny punkt awarii DNS.
Logo: background-image, login_headerurl, login_headertext
WordPress nie ma filtra „podmień plik logo”. Klasyczny wzorzec to CSS na #login h1 a / .login h1 a (background-image) plus dwa filtry na atrybuty linku.
Podmiana grafiki przez login_enqueue_scripts:
function wppoland_login_logo() {
$logo = get_stylesheet_directory_uri() . '/assets/images/logo-logowania.svg';
?>
<style type="text/css">
#login h1 a,
.login h1 a {
background-image: url(<?php echo esc_url( $logo ); ?>);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 320px;
height: 80px;
margin: 0 auto 16px;
}
</style>
<?php
}
add_action( 'login_enqueue_scripts', 'wppoland_login_logo' );esc_url() na ścieżce assetu to obowiązek, nawet gdy budujesz URL z get_stylesheet_directory_uri(). Logo trzymaj w SVG lub PNG z przezroczystością; JPEG ze zbędnym białym tłem wygląda jak naklejka na ciemnym formularzu.
Domyślny href logo prowadzi na wordpress.org. Filtr login_headerurl zmienia cel:
function wppoland_login_logo_url() {
return home_url( '/' );
}
add_filter( 'login_headerurl', 'wppoland_login_logo_url' );Tekst dostępności (title / zawartość dla czytników) ustawia login_headertext (dawniej login_headertitle - starsze snippetaty w internecie nadal go cytują):
function wppoland_login_logo_title() {
return get_bloginfo( 'name' );
}
add_filter( 'login_headertext', 'wppoland_login_logo_title' );Na instalacjach z osobną domeną kokpitu (np. cms.klient.pl vs www.klient.pl) home_url() nadal wskazuje front, co jest zwykle pożądane. Jeśli logo ma prowadzić do dokumentacji wewnętrznej albo do IdP, zwróć pełny URL HTTPS i przetestuj w oknie incognito.
login_message: komunikaty nad formularzem
Filtr login_message dokłada HTML nad formularzem. Typowe zastosowania produkcyjne:
- Informacja o oknie utrzymaniowym („Logowanie chwilowo wyłączone poza VPN”).
- Wskazówka dla redaktorów („Używaj konta firmowego, nie prywatnego Gmaila”).
- Banner po wylogowaniu albo po resecie hasła (WordPress i tak dokłada własne message classes).
- Link do SSO / „Zaloguj przez Microsoft”, jeśli IdP jest obok klasycznego hasła.
/**
* Komunikat nad formularzem logowania.
*
* @param string $message Istniejący HTML.
* @return string
*/
function wppoland_login_message( $message ) {
$notice = '<p class="message">' . esc_html__(
'Konto służbowe tylko dla zespołu redakcyjnego. Podejrzenie wycieku hasła zgłoś do IT.',
'wppoland'
) . '</p>';
return $notice . $message;
}
add_filter( 'login_message', 'wppoland_login_message' );Uwagi wdrożeniowe:
- Escapuj tekst (
esc_html__) albo buduj markup przezwp_kses_post, jeśli musisz wstawić link. - Nie zastępuj całego
$messageślepo - WordPress dokłada tu własne komunikaty (np. pologgedout=true). Doklejaj albo filtruj warunkowo po$_REQUEST. - Klasa
.messagejest już ostylowana przezlogin.css; Twójstyle-login.csspowinien ją respektować (kolor tekstu, border-left), inaczej banner znika w tle.
Dla wariantów zależnych od akcji (login, lostpassword, rp) sprawdzaj isset( $_GET['action'] ) albo globalne kontekstowe flagi, zamiast pokazywać ten sam tekst na odzyskiwaniu hasła i na ekranie 2FA.
Bezpieczeństwo: komunikaty błędów i mit „ukryj wp-login CSS-em”
Domyślny komunikat przy złym haśle potrafi zdradzić, że użytkownik o danej nazwie istnieje. To ułatwia enumerację kont. Filtr login_errors pozwala zwrócić jedną, generyczną treść:
function wppoland_login_errors() {
return __( 'Logowanie nie powiodło się. Sprawdź dane i spróbuj ponownie.', 'wppoland' );
}
add_filter( 'login_errors', 'wppoland_login_errors' );To utrudnia reconnaissance. To nie jest firewall, rate limiting ani 2FA. Nadal potrzebujesz:
- limitów prób (serwer, WAF, albo sensowna wtyczka / hardening na poziomie hostingu),
- silnych haseł i najlepiej drugiego składnika (aplikacja TOTP, klucz sprzętowy),
- aktualnego WordPressa, motywów i wtyczek,
- ograniczenia dostępu do
wp-admin/ XML-RPC tam, gdzie polityka firmy na to pozwala (VPN, IP allowlist, HTTP auth na stagingu).
Najczęstszy antypatern w briefach klientów: „ukryjemy logowanie CSS-em / wyślemy /wp-login.php na 404 w .htaccess i będziemy mieli security”. Dlaczego to nie działa:
- Security through obscurity - boty i skanery znają ścieżki domyślne; zmiana URL bez twardej kontroli dostępu tylko psuje bookmarki redaktorów.
- CSS nic nie chowa po stronie serwera -
display: nonena linku „Zaloguj” w menu nie usuwa endpointu.wp-login.phpiwp-adminnadal odpowiadają. - Własny slug logowania (wtyczki typu rename login) bywa użyteczny jako warstwa tarcia, ale wymaga utrzymania, testów resetu hasła, WooCommerce My Account, REST i cronów. Samo CSS albo
robots.txtDisallow nie zastąpi polityki haseł. - Komunikaty REST / XML-RPC nadal mogą zdradzać istnienie użytkowników niezależnie od brandingu ekranu.
Branding logowania i bezpieczeństwo to dwa tory. White-label poprawia UX i zaufanie. Ochrona konta to capability, rate limit, 2FA i aktualizacje. Nie łącz ich w jeden ticket „zrób ładne i bezpieczne samym CSS”.
Jeśli w projekcie jest wtyczka Two Factor albo passkeys, po wdrożeniu CSS sprawdź ekrany dodatkowych metod (backup codes, security key). Selektor .login form często dostaje więcej pól; zbyt agresywne height / overflow: hidden na #login ucina je w połowie.
mu-plugin kontra motyw potomny
Gdzie trzymać kod? Dwie sensowne lokalizacje, jedna zła.
Motyw potomny (functions.php + assets/css/style-login.css)
- Zalety: logo i kolory jadą razem z designem frontu; designer zmienia pliki w jednym repozytorium motywu; wersjonowanie przez
Versionwstyle.css. - Wady: przełączenie motywu (awaria, test A/B, migracja na blokowy) wyłącza branding logowania. Na multisite z różnymi motywami per site kod trzeba powielać albo przenosić wyżej.
Must-use plugin (wp-content/mu-plugins/wppoland-login-branding.php)
- Zalety: działa niezależnie od aktywnego motywu; idealne na sieć agencyjną i na instalacje, gdzie motyw bywa wymieniany; trudniej „przypadkiem” wyłączyć z kokpitu.
- Wady: assety (logo, CSS) musisz ładować z katalogu mu-pluginu (
plugin_dir_url( __FILE__ )) albo z uploads; deploy mu-pluginów bywa pomijany w workflow „tylko motyw”.
Zła lokalizacja: edycja plików w wp-admin/ albo patch wp-login.php w rdzeniu. Znika przy aktualizacji. Unikaj też „snippetów” wstawianych przez wtyczkę do bazy bez kontroli wersji - przy migracji staging → prod giną albo dublują się.
Wzorzec mu-pluginu z enqueue:
<?php
/**
* Plugin Name: WPPoland Login Branding
* Description: White-label wp-login.php niezależnie od motywu.
* Author: wppoland.com
*/
defined( 'ABSPATH' ) || exit;
add_action( 'login_enqueue_scripts', function () {
$base = plugin_dir_url( __FILE__ );
wp_enqueue_style(
'wppoland-login',
$base . 'assets/style-login.css',
array(),
'2026.09.21'
);
} );
add_filter( 'login_headerurl', function () {
return home_url( '/' );
} );
add_filter( 'login_headertext', function () {
return get_bloginfo( 'name' );
} );Reguła decyzyjna w briefie: jeśli branding logowania jest częścią produktu klienta (kolory marki, logo kampanii) - motyw potomny. Jeśli to standard agencyjny na wielu instalacjach (to samo logo agencji na stagingach, generyczne błędy, wyłączony shake) - mu-plugin albo wspólny pakiet Composer / prywatne repo wdrażane na wszystkie środowiska.
Branding bez łamania aktualizacji rdzenia
Cel: wygląd własny, zachowanie jak w Core. Checklista, która trzyma ten kontrakt:
- Tylko hooki publiczne -
login_enqueue_scripts,login_headerurl,login_headertext,login_message,login_errors, ewentualnielogin_footer. Nie nadpisuj template’ów zwp-login.phpprzezrequireskopiowanego pliku. - Nie usuwaj zależnych stylów Core na ślepo -
wp_dequeue_style( 'login' )bez własnego pełnego arkusza psuje dostępność i layout przycisków po aktualizacji. - Shake JS możesz wyłączyć świadomie, jeśli klient uważa animację za nieprofesjonalną:
function wppoland_remove_login_shake() {
remove_action( 'login_head', 'wp_shake_js', 12 );
}
add_action( 'login_head', 'wppoland_remove_login_shake' );Po major update sprawdź, czy priorytet 12 i nazwa callbacka nadal obowiązują (changelog / blame w Core).
- Test regresji po każdej aktualizacji WordPressa: poprawne hasło, złe hasło, puste pola, lost password, reset link z maila, ewentualnie 2FA. Porównaj screenshot stagingu z produkcją.
- Języki - jeśli strona jest PL, a kokpit EN (albo odwrotnie), komunikaty z
login_errorsilogin_messagetrzymaj w textdomainie i ładuj tłumaczenia; hardkodowany polski string na angielskim kokpicie wygląda jak niedokończone wdrożenie. - Multisite -
home_url()jest per-site; logo z mu-pluginu może wymagaćswitch_to_blogalbo assetów per blog. Nie zakładaj jednego pliku SVG na całą sieć bez uzgodnienia. - Cache i CDN - HTML logowania zwykle omija page cache, ale CSS z motywu bywa cache’owany agresywnie. Po deployu bump wersji enqueue.
Gdy klient prosi o „całkowicie własny ekran logowania jak w SaaS”, rozważ osobną stronę frontową z formularzem opartym o wp_signon() / custom auth tylko jeśli masz budżet na utrzymanie resetu hasła, nonce, lockout i zgodność z wtyczkami membership. W 80% projektów agencyjnych wystarczy ostylowany wp-login.php. Nadmiarowa aplikacja logowania to drugi produkt do łatania przy każdej lukę w auth.
Checklista wdrożenia (15 minut na staging)
Przejdź po kolei na kopii produkcyjnej:
- Wgraj CSS i logo; odśwież
wp-login.phpz pominięciem cache (okno incognito). - Kliknij logo - lądujesz na froncie klienta, nie na wordpress.org.
- Zaloguj się poprawnym kontem; wyloguj; zaloguj złym hasłem - komunikat jest generyczny.
- Sprawdź „Nie pamiętasz hasła?” i mail resetu na środowisku ze skonfigurowanym SMTP.
- Jeśli jest 2FA - przejdź pełną ścieżkę raz na desktopie i raz na telefonie.
- Przełącz tymczasowo motyw na Twenty Twenty-Something: jeśli branding znika, a miał być globalny, przenieś kod do mu-pluginu.
- Zapisz w README projektu ścieżki plików i listę filtrów - kolejny deweloper nie szuka „magii” w pięciu wtyczkach snippet.
Po akceptacji klienta zrób ten sam smoke test na produkcji zaraz po deployu, nie „jutro”.
Podsumowanie
White-label wp-login.php w 2026 to zestaw publicznych hooków, nie patch rdzenia i nie wtyczka z tysiącem opcji w kokpicie. Enqueue CSS przez login_enqueue_scripts, podmień logo i login_headerurl, doklej sensowny login_message, utwardź login_errors. Trzymaj kod w motywie potomnym albo mu-pluginie - nigdy w plikach Core. Nie udawaj zabezpieczenia ukryciem URL w CSS.
Gdy porządkujesz ryzyka wokół kont i kokpitu szerzej niż branding, zobacz audyt bezpieczeństwa WordPress.






