La pantalla de login predeterminada de WordPress (wp-login.php) es funcional y reconocible. En un proyecto de cliente sigue leyéndose como «CMS de serie», no como su producto. Poner marca blanca en el login es un cambio pequeño con mucho acabado: logo, colores, errores más discretos y CSS que solo carga en esa URL.
Esta guía usa solo hooks del núcleo, contrastados con el código de WordPress 7.1. Sin plugin de branding. Pon el código en un tema hijo o en un plugin pequeño del sitio (o must-use) para que una actualización del tema padre no lo borre. Si el sitio ya tiene historial de exposición, el endurecimiento que va más allá de lo cosmético encaja mejor tras revisar el entorno; puedes empezar por contacto cuando haga falta encajar marca y endurecimiento en el mismo trabajo.
Por qué personalizar el login de WordPress
Editores y clientes llegan a wp-login.php más a menudo que a muchas páginas de marketing. Un logo de WordPress junto a los colores de su marca rompe la confianza antes de llegar al escritorio. Las agencias también usan un login con marca como señal suave de que la instalación se cuidó, no se dejó en valores por defecto.
Personalizar no es seguridad por sí sola. Cambiar el logo no detiene el credential stuffing. Combina la marca con texto de error genérico, límites de intentos (host o plugin) y 2FA en cuentas privilegiadas.
El briefing típico del cliente: «Que parezca nuestra app». Eso suele significar logo, fondo, color del botón principal y enlaces de pie que apuntan a su dominio. No significa reescribir la autenticación. Mantén el alcance honesto en el presupuesto para que nadie espere un SSO a partir de un ticket de CSS.
En redes multisite a menudo se quiere un login con marca para todos los sitios. Pon el CSS compartido en un must-use o en un plugin de red.
Qué carga WordPress en wp-login.php
Antes de cambiar el logo conviene saber qué hace el núcleo cuando se pide wp-login.php:
- Carga
wp-admin/css/login.cssy los estilos del escritorio de los que depende (botones, formularios). - Ejecuta
login_enqueue_scripts, donde añades tu CSS y JS, igual quewp_enqueue_scriptsen el front. - Pinta la cabecera con el enlace del logo (por defecto a wordpress.org), el formulario, los enlaces de contraseña olvidada y registro, y el pie.
- Ante un error de validación añade el script del temblor y muestra el mensaje del filtro
login_errors. - Los filtros
login_messageylogin_messagespermiten colocar HTML encima del formulario (aviso tras cerrar sesión, nota de mantenimiento, banner de SSO).
La consecuencia clave: la página de login no pasa por el pipeline del tema del front. header.php, las plantillas de un tema de bloques y la mayoría de callbacks de wp_enqueue_scripts no se ejecutan ahí. Por eso existe una acción de encolado propia y por eso los estilos del login necesitan su propio archivo. Las reglas que solo viven en el style.css del tema nunca llegan a wp-login.php.
La segunda consecuencia: una actualización de WordPress puede cambiar selectores, el orden de los scripts o el markup del formulario (campos extra de un plugin de 2FA, por ejemplo). La marca construida con hooks sobrevive a la actualización; un wp-login.php parcheado en el núcleo, no.
Cambiar el logo del login de WordPress sin plugin
Engancha login_enqueue_scripts y sobrescribe la imagen de fondo en #login h1 a.
function wppoland_login_logo() {
?>
<style type="text/css">
#login h1 a,
.login h1 a {
background-image: url(<?php echo esc_url( get_stylesheet_directory_uri() . '/images/cliente-logo.svg' ); ?>);
height: 65px;
width: 320px;
background-size: contain;
background-repeat: no-repeat;
padding-bottom: 30px;
}
</style>
<?php
}
add_action( 'login_enqueue_scripts', 'wppoland_login_logo' );Usa esc_url en la ruta de la imagen. Prefiere SVG o un PNG listo para retina; un JPEG grande se ve blando en pantallas HiDPI. Guarda el archivo en el tema hijo para que los deploys sean predecibles.
Por defecto el logo enlaza a wordpress.org. Apúntalo al inicio del sitio:
function wppoland_login_logo_url() {
return home_url( '/' );
}
add_filter( 'login_headerurl', 'wppoland_login_logo_url' );Opcionalmente filtra login_headertext para que el texto accesible del enlace coincida con el nombre del cliente y no con el texto por defecto de WordPress. Sustituyó a login_headertitle, obsoleto desde WordPress 5.2; los snippets antiguos todavía lo usan.
function wppoland_login_logo_title() {
return get_bloginfo( 'name' );
}
add_filter( 'login_headertext', 'wppoland_login_logo_title' );En instalaciones con un dominio aparte para el escritorio (cms.cliente.es junto a www.cliente.es), home_url() sigue apuntando al sitio público, que suele ser lo deseado. Si el logo debe llevar a documentación interna o al proveedor de identidad, devuelve una URL HTTPS completa y pruébala en una ventana privada.
Añadir un mensaje encima del formulario de login de WordPress
El filtro login_message coloca HTML encima del formulario. Usos habituales en producción:
- Un aviso de mantenimiento («Esta noche no se puede entrar fuera de la VPN»).
- Una indicación para editores («Usa la cuenta de empresa, no una personal»).
- Un banner tras cerrar sesión o restablecer la contraseña (el núcleo también añade ahí sus propias clases de mensaje).
- Un enlace a SSO cuando el proveedor de identidad convive con las contraseñas clásicas.
/**
* Mensaje encima del formulario de login.
*
* @param string $message HTML existente.
* @return string
*/
function wppoland_login_message( $message ) {
$notice = '<p class="message">' . esc_html__(
'Cuentas solo para el equipo. Si sospechas una fuga de contraseña, avisa a IT.',
'your-textdomain'
) . '</p>';
return $notice . $message;
}
add_filter( 'login_message', 'wppoland_login_message' );Notas de implementación:
- Escapa el texto (
esc_html__), o construye el markup conwp_kses_postsi necesita un enlace. - No sustituyas
$messagea ciegas. El núcleo coloca ahí sus propios avisos (después deloggedout=true, por ejemplo). Antepón tu texto o filtra de forma condicional según la petición. - La clase
.messageya tiene estilos enlogin.css. Tustyle-login.cssdebe respetarla (color del texto, borde izquierdo), o el banner desaparece contra el fondo.
Para textos que dependen de la acción (login, lostpassword, rp), comprueba $_GET['action'] en lugar de mostrar la misma nota en la recuperación de contraseña y en el paso de 2FA.
Añadir CSS personalizado al login de WordPress
No vuelques reglas de login en el style.css público. Encola un archivo dedicado en el mismo hook:
function wppoland_login_stylesheet() {
wp_enqueue_style(
'wppoland-custom-login',
get_stylesheet_directory_uri() . '/style-login.css',
array(),
wp_get_theme()->get( 'Version' )
);
}
add_action( 'login_enqueue_scripts', 'wppoland_login_stylesheet' );Ejemplo de style-login.css:
body.login {
background-color: #0d1117;
display: flex;
align-items: center;
justify-content: center;
}
.login form {
background: #161b22;
border: 1px solid #30363d;
box-shadow: none;
border-radius: 8px;
}
.login label {
color: #c9d1d9;
}
.wp-core-ui .button-primary {
background: #238636;
border-color: rgba(27, 31, 35, 0.15);
}
.login #backtoblog a,
.login #nav a {
color: #c9d1d9;
}Ajusta el contraste a WCAG cuando los editores lo necesiten: gris claro sobre gris oscuro falla para algunos usuarios. Prueba los outlines de foco en usuario, contraseña y envío; quitar outlines por «diseño limpio» es una regresión.
Los paquetes de idioma y las locales RTL siguen usando los mismos hooks. Evita cadenas en inglés fijas en propiedades CSS content.
Las imágenes de fondo en body.login deben estar comprimidas. Prefiere un color de marca sólido o un patrón sutil por debajo de 100 KB. Si el manual exige un formulario claro sobre fondo claro, sube el contraste del borde en .login form para que la tarjeta no se funda con la página.
Cambiar el mensaje de error del login de WordPress
Un fallo de contraseña para un usuario existente históricamente producía un mensaje que confirmaba el nombre de usuario. Eso ayuda a enumerar cuentas antes de forzar contraseñas.
function wppoland_login_errors() {
return __( 'Credenciales no válidas.', 'your-textdomain' );
}
add_filter( 'login_errors', 'wppoland_login_errors' );Mantén el texto aburrido e idéntico para usuario desconocido y contraseña incorrecta. Esto es ofuscación frente a la enumeración, no un sustituto de:
- Prohibir contraseñas de administración débiles.
- Limitar intentos de login en la aplicación o en el edge.
- Activar 2FA para administradores.
- No usar
admincomo nombre de usuario en instalaciones nuevas.
Los flujos de contraseña perdida tienen sus propios mensajes. Revísalos si personalizas registro o plugins de membresía que comparten el CSS de login.
Algunos plugins de membresía sustituyen wp-login.php por un formulario en el front. En ese caso estos hooks no se disparan. Aplica las mismas reglas en los hooks del plugin, o estiliza sus plantillas parciales. Confirma qué URL usan realmente los editores antes de dar el trabajo por cerrado.
XML-RPC y el endpoint REST de usuarios son superficies de ataque aparte. Marcar wp-login.php no hace nada por ellas. Desactiva o restringe XML-RPC si no lo necesitas, y mantén las contraseñas de aplicación bajo política si el sitio las usa.
Desactivar la animación shake del login de WordPress
Los logins fallidos activan un temblor vía wp_shake_js. Algunas guías de marca lo tratan como ruido.
El snippet que siguen copiando casi todos los tutoriales, remove_action( 'login_head', 'wp_shake_js', 12 ), no hace nada desde WordPress 5.4. El núcleo engancha ahora wp_shake_js a login_footer, y solo de forma condicional, dentro de login_header(). Se ve en wp-login.php del repositorio wordpress-develop; la rama 5.3 todavía usaba login_head. El método estable es el filtro shake_error_codes, disponible desde la 3.0: una lista vacía de códigos de error significa que no hay animación.
add_filter( 'shake_error_codes', '__return_empty_array' );El filtro es API pública, así que no depende de la prioridad ni del hook desde el que el núcleo imprima el script. Documenta la intención en un comentario de una línea encima.
Cómo personalizar 2FA y passkeys en wp-login.php
Construir 2FA completo dentro de functions.php es el proyecto equivocado. Usa un plugin mantenido (por ejemplo el plugin oficial Two Factor o la oferta de tu hosting) y estiliza los campos extra para que encajen con el formulario de marca.
El plugin Two Factor del directorio de WordPress.org (versión 0.17.0, más de 100.000 instalaciones activas según la API del directorio en octubre de 2026) emite estas clases en el segundo paso del login. Están sacadas del código de la 0.17.0:
.two-factor-promptpara el párrafo de instrucciones,input.authcode(conid="authcode") para el código TOTP, el código por correo y el código de respaldo; el campo llevainputmode="numeric"yautocomplete="one-time-code", así que no sobrescribas esos atributos con un script, o el móvil deja de ofrecer el código del SMS o del gestor de contraseñas,.backup-methods-wrappara los enlaces a métodos alternativos,.two-factor-email-resendpara el botón que reenvía el código por correo.
.login .two-factor-prompt {
color: #c9d1d9;
}
.login input.authcode {
letter-spacing: 0.2em;
text-align: center;
}
.login .backup-methods-wrap a {
color: #c9d1d9;
}Los tutoriales antiguos usan selectores como .backup-methods o .two-factor-methods. El código actual del plugin no tiene ninguno de los dos, así que esas reglas nunca aplican. Carga las reglas que sí funcionan en el mismo style-login.css. Tras cada actualización del plugin, vuelve a revisar el segundo paso del login en staging, porque los nombres de clase se mueven. Otros plugins de 2FA y passkeys tienen su propio markup: lee sus selectores en su código, no en esta guía.
La UX de passkeys y WebAuthn varía según el plugin. En el lado de marca tu trabajo es contraste, espaciado y no recortar el prompt de la llave de seguridad con overflow: hidden en .login form.
Tema hijo o plugin para personalizar el login de WordPress
| Ubicación | Pros | Contras |
|---|---|---|
| Tema hijo | Viaja con assets del tema y la ruta del logo | Se pierde si alguien cambia de tema |
| Plugin específico del sitio | Sobrevive a cambios de tema | La URL del logo no debe hardcodear la ruta del tema antiguo |
| Must-use | Difícil de desactivar por accidente | Necesita acceso de deploy; no ideal para todos los clientes |
Las agencias que entregan el sitio suelen preferir un plugin pequeño con el nombre del cliente y el logo en assets/. Así la marca del login se mantiene cuando marketing instala después un tema de bloques nuevo.
Una versión como plugin must-use con el encolado y los dos filtros del logo:
<?php
/**
* Plugin Name: Login Branding
* Description: Marca blanca de wp-login.php sin depender del tema activo.
*/
defined( 'ABSPATH' ) || exit;
add_action( 'login_enqueue_scripts', function () {
wp_enqueue_style(
'site-login',
plugin_dir_url( __FILE__ ) . 'assets/style-login.css',
array(),
'2026.10.14'
);
} );
add_filter( 'login_headerurl', function () {
return home_url( '/' );
} );
add_filter( 'login_headertext', function () {
return get_bloginfo( 'name' );
} );Una regla para el briefing: si la marca del login forma parte del producto del cliente (colores de marca, logo de campaña), usa el tema hijo. Si es un estándar de la agencia en muchas instalaciones (el mismo logo de agencia en todos los staging, errores genéricos, sin temblor), usa un plugin must-use o un paquete Composer compartido que se despliegue en todos los entornos.
No guardes este código en un plugin de snippets que almacena PHP en la base de datos. Sin control de versiones se pierde o se duplica al pasar de staging a producción.
Errores al personalizar el login de WordPress
- No edites
wp-login.phpen el núcleo. Las actualizaciones lo sobrescriben. - No encoles bundles frontend pesados en el login; mantén el CSS ligero.
- No ocultes la URL de login y llames a eso «seguridad» mientras dejas
admin/password123. - No apiles tres plugins de personalización de login encima de estos hooks.
- No pongas secretos ni claves API en el CSS de login.
Cambiar el slug de login (vía un plugin dedicado) es una decisión aparte. Puede recortar bots casuales; también rompe marcadores y algunos checklists de deploy. Marca y cambio de slug son independientes.
Si renombras la ruta de login, documenta la nueva URL en la entrega y en el gestor de contraseñas del cliente.
Cómo personalizar el login de WooCommerce y plugins de membresía
Las páginas de cuenta de WooCommerce y los bloques de login en checkout usan otro markup. El CSS de login no restiliza my-account. O aceptas los formularios de Woo o encolas una hoja aparte. Mezclar ambos en style-login.css crea reglas muertas.
La misma separación aplica a LearnDash, MemberPress y similares: identifica la URL de entrada real. Un error habitual es pulir wp-login.php mientras los clientes solo ven un modal front con el logo de WordPress.
Cómo revisar el login tras actualizar WordPress
Las versiones mayores de WordPress a veces retocan el markup o el CSS por defecto del login. Tras actualizar el núcleo en staging:
- Diff el HTML de login si algo se ve mal.
- Vuelve a probar el tamaño del logo; nuevas alturas por defecto pueden recortar SVG.
- Si desactivaste el temblor, confirma que el filtro
shake_error_codessigue aplicándose. - Revisa los campos de 2FA si ese plugin se actualizó en la misma ventana.
Fija la versión de la hoja de estilos a la del tema o del plugin para que el navegador no conserve un style-login.css antiguo tras un arreglo de contraste.
Qué hooks de login tiene WordPress y desde qué versión
La tabla sale de las etiquetas @since de src/wp-login.php en el repositorio wordpress-develop (octubre de 2026, versión actual WordPress 7.1.3). Sirve cuando un cliente mantiene una instalación antigua o cuando un snippet de un foro usa un hook que todavía no existe o que ya está obsoleto.
| Hook | Tipo | Desde | Para qué sirve |
|---|---|---|---|
login_headerurl | filtro | 2.1.0 | URL del enlace del logo |
login_message | filtro | 2.1.0 | HTML encima del formulario |
login_errors | filtro | 2.1.0 | texto de los errores |
login_messages | filtro | 2.5.0 | avisos informativos |
login_redirect | filtro | 3.0.0 | destino de la redirección tras el login |
shake_error_codes | filtro | 3.0.0 | códigos de error que hacen temblar el formulario |
login_enqueue_scripts | acción | 3.1.0 | CSS y JS de la pantalla de login |
login_footer | acción | 3.1.0 | salida en el pie |
login_init | acción | 3.2.0 | inicio del procesamiento de la petición |
login_body_class | filtro | 3.5.0 | clases del elemento body |
login_header | acción | 4.6.0 | salida justo tras abrir el body |
login_title | filtro | 4.9.0 | contenido de la etiqueta title |
login_link_separator | filtro | 4.9.0 | separador de los enlaces bajo el formulario |
login_headertext | filtro | 5.2.0 | texto del enlace del logo |
login_site_html_link | filtro | 5.7.0 | enlace de vuelta al sitio en el pie |
login_display_language_dropdown | filtro | 5.9.0 | si se muestra el selector de idioma |
login_language_dropdown_args | filtro | 5.9.0 | argumentos de la lista de idiomas |
login_headertitle (desde 2.1.0) está marcado como obsoleto desde la 5.2.0 y pasa por apply_filters_deprecated, así que con WP_DEBUG activo deja un aviso en el log. Si el tema de un cliente todavía lo usa, cámbialo por login_headertext.
Cambiar el título, la clase del body y el selector de idioma en wp-login.php
Cuatro filtros de la tabla resuelven peticiones pequeñas del briefing sin tocar CSS:
// Título de pestaña más corto: nombre de la pantalla y nombre del sitio.
add_filter( 'login_title', function ( $login_title, $title ) {
return $title . ' | ' . get_bloginfo( 'name', 'display' );
}, 10, 2 );
// Clase propia en el body para que las reglas del login no choquen con otras hojas.
add_filter( 'login_body_class', function ( $classes ) {
$classes[] = 'site-login';
return $classes;
} );
// El enlace del pie lleva a una página de ayuda para editores en lugar del inicio.
add_filter( 'login_site_html_link', function () {
return sprintf(
'<a href="%s">%s</a>',
esc_url( home_url( '/ayuda/' ) ),
esc_html__( 'Ayuda para editores', 'your-textdomain' )
);
} );
// Sin lista de idiomas bajo el formulario.
add_filter( 'login_display_language_dropdown', '__return_false' );Detalles que solo aparecen en el código del núcleo:
- El título por defecto combina el nombre de la pantalla, el nombre del sitio y la palabra WordPress al final. En modo de recuperación el núcleo añade un prefijo antes de aplicar el filtro. Un callback que construye el título solo con
$titlepierde ese prefijo, y el administrador deja de ver en la pestaña que el sitio está en modo de recuperación. Si eso importa, modifica$login_titleen lugar de sustituirlo. - La lista de idiomas solo aparece cuando hay al menos un paquete de idioma adicional instalado, y nunca en el modal de reconexión del escritorio. Ocultarla no cambia el idioma de la cuenta: tras el login se aplica el ajuste del perfil.
login_site_html_linksustituye el elemento<a>completo, así que escapar la URL y el texto es cosa tuya.- El enlace a la política de privacidad bajo el formulario sale de
the_privacy_policy_link()y aparece cuando hay una página de privacidad configurada. No lo ocultes con CSS en sitios que recogen datos con un formulario de registro.
Qué cabeceras de seguridad envía WordPress en wp-login.php
Antes de añadir tus propias cabeceras o meta etiquetas a la pantalla de login, revisa lo que el núcleo ya hace. Según default-filters.php y wp-login.php:
send_frame_options_header()enlogin_init(desde 3.1.3) envíaX-Frame-Options: SAMEORIGIN, y desde WordPress 6.9 la misma función añadeContent-Security-Policy: frame-ancestors 'self'. La pantalla de login no se puede incrustar en un iframe de otro dominio.wp_admin_headers()enlogin_init(desde 4.9) envíaReferrer-Policy: strict-origin-when-cross-origin; el filtroadmin_referrer_policycambia el valor.wp_robots_sensitive_page()(desde 5.7) añadenoindexynoarchiveal meta robots, que imprimewp_robotsenlogin_head.wp_strict_cross_origin_referrer()(desde 5.7) añade una meta etiqueta referrer con la misma política.wp_site_iconenlogin_headimprime el favicon del ajuste Icono del sitio.
En la práctica, un snippet aparte que imprime un meta robots noindex o un enlace al favicon en login_head solo duplica etiquetas. No añadas X-XSS-Protection. MDN la describe como no estándar y obsoleta, advierte que puede crear vulnerabilidades XSS en sitios por lo demás seguros y recomienda Content-Security-Policy. Las cabeceras de todo el sitio (HSTS, CSP) van en la configuración del servidor o de la CDN, no en PHP sobre login_init, para que cubran también wp-admin y REST.
Cómo probar el login personalizado de WordPress
- Abre
/wp-login.phpsin sesión; confirma logo, colores y enlace al inicio. - Envía una contraseña incorrecta; confirma el error genérico y la ausencia de shake si lo quitaste.
- Confirma que «¿Olvidaste la contraseña?» sigue funcionando y que llegan los correos.
- Con 2FA activo, completa un login de administrador en staging.
- Cambia a un segundo idioma si el sitio es multilingüe y revisa el layout.
- Entra desde un viewport móvil; el centrado con flex a veces colapsa el formulario en pantallas bajas.
Resumen
Marca el logo y la URL del encabezado, encola CSS solo para login, devuelve errores genéricos y, si quieres, quita el shake. Mantén el código fuera del tema padre. Trata el 2FA como un control real y estiliza su UI; trata el CSS del logo como acabado.
Entrega al cliente una nota de una página: dónde vive el código, cómo sustituir el archivo del logo y cuál es la URL canónica para el personal. Esa nota evita que el siguiente freelance instale un plugin de login redundante seis meses después.
Si la instalación ya muestra indicios de abuso, o necesitas revisar el endurecimiento del login junto con plugins y hosting, empieza por contacto en lugar de apilar otro plugin cosmético sobre un sitio comprometido.







