“The best error message is the one that never shows up.” – Thomas Fuchs

Ever had a client insist something isn’t working, even though it works perfectly fine on your end? This happened recently with the Portland Winter Light Festival’s artist portal. We’d created a password-protected page for participating artists – a logical choice using WordPress’s built-in protection feature. Simple enough, right?

Even after simplifying the password, artists kept reporting access issues. As developers, our knee-jerk reaction is often to blame user error. But I started thinking – if multiple users are entering incorrect passwords, isn’t that actually a UX problem?

Here’s the kicker: WordPress wasn’t giving users any feedback when they enter an incorrect password for protected pages. None. Zero. Zilch. [as of 2/19/2026 WP does seem to be properly showing “Invalid password” now, so something must have changed since I wrote this in Nov 2024]

So I wrote a few functions to fix that:

Display Incorrect Password Notice to User

<?php
/**
 * Enhanced Password Protection
 *
 * Adds error messaging to WordPress password protected posts/pages
 * while maintaining core WordPress password form functionality.
 *
 * @link https://studiok40.com
 * @package pdxwlf
 * @since 1.0.0
 */

/**
 * Adds error message handling for password protected content.
 *
 * Only loads on password protected singular pages that haven't been unlocked.
 * Uses minimal DOM operations and cleans up URL parameters automatically.
 *
 * @since 1.0.0
 *
 * @return void
 */
function prefix_handle_password_error() {
	// Early exit if not needed.
	if ( ! is_singular() || ! post_password_required() ) {
		return;
	}

	?>
	<script>
	document.addEventListener('DOMContentLoaded', function() {
		const urlParams = new URLSearchParams(window.location.search);
		const hasError = urlParams.get('password-error') === '1';

		if (hasError) {
			const form = document.querySelector('.post-password-form');
			if (form) {
				const errorDiv = document.createElement('div');
				errorDiv.classList.add('password-error');
				errorDiv.style.cssText = 'color: #dc3232; margin-bottom: 1em;';
				errorDiv.textContent = <?php echo wp_json_encode( __( 'Incorrect password. Please try again.', 'text-domain' ) ); ?>;
				form.insertBefore(errorDiv, form.firstChild);

				// Clean up URL parameters
				const cleanUrl = window.location.pathname;
				window.history.replaceState({}, '', cleanUrl);
			}
		}
	});
	</script>
	<?php
}
add_action( 'wp_footer', 'prefix_handle_password_error' );

/**
 * Handles password verification and error redirection.
 *
 * Checks submitted passwords against the post's password and
 * redirects with error parameter on failure.
 *
 * @since 1.0.0
 *
 * @return void
 **/
function prefix_check_password_failed() {
	if ( ! isset( $_POST['post_password'] ) ) {
		return;
	}

	$referer = wp_get_referer();
	if ( ! $referer ) {
		return;
	}

	$post_id = url_to_postid( $referer );
	if ( ! $post_id || ! ( $post = get_post( $post_id ) ) || empty( $post->post_password ) ) {
		return;
	}

	// Verify password.
	if ( ! hash_equals( $post->post_password, wp_unslash( $_POST['post_password'] ) ) ) {
		wp_safe_redirect( add_query_arg( 'password-error', '1', $referer ) );
		exit;
	}
}

add_action( 'login_form_postpass', 'prefix_check_password_failed', 5 );

Which results in:

Better! Now users know when they’ve entered the wrong password. But why wait until they hit submit?

Make the Password Text Visible on Field Focus

Modern web interfaces commonly feature an “eye” icon on password fields, letting users verify their input.

Given that we’re just protecting event information (not nuclear launch codes), we can go a step further.

Instead of making users click an eye icon, I created a function that automatically displays the password in plain text while the field is active, then hides it when focus moves elsewhere. Less friction, same security. I inline the small JavaScript to avoid an asset load:

function enqueue_password_toggle_script() {
	// Only proceed if we're on a password-protected page.
	if ( ! post_password_required() ) {
		return;
	}

	// Define the inline script.
	$script = "document.addEventListener('DOMContentLoaded',function(){const e=document.querySelector('input[name=\"post_password\"]'),t=document.querySelector('input[name=\"Submit\"]');e.addEventListener('focus',function(){this.setAttribute('type','text')}),e.addEventListener('blur',function(){document.activeElement.getAttribute('name')!=='Submit'&&this.setAttribute('type','password')}),t.addEventListener('focus',function(){e.setAttribute('type','password')})});";

	// Add inline script with a unique handle.
	wp_register_script( 'password-toggle', false, array(), '1.0.0', true );
	wp_enqueue_script( 'password-toggle' );
	wp_add_inline_script( 'password-toggle', $script );
}
add_action( 'wp_enqueue_scripts', 'enqueue_password_toggle_script' );

Which looks like this on the front end:

Provide Access to a Password Protected Page via URL

But why stop there? The best error message is the one users never see. Since we’re emailing the password to artists anyway, why make them type it at all? I added functionality to accept the password as a URL parameter, letting artists access the content with a single click from their email

/**
 * Allow password protected pages to be accessed via a URL query parameter.
 * to use, add ?pwd=theActualPagePassword to the end of the URL
 */
add_filter( 'post_password_required', function( $returned, $post ) {

	if ( $returned && ( isset( $_GET['pwd'] ) && $_GET['pwd'] === $post->post_password ) ) {
		$returned = false;
	}
	return $returned;

}, 10, 2 );

This is what modern web development should be: eliminating friction while maintaining appropriate security.

“Your objective should always be to eliminate instructions entirely by making everything self-explanatory, or as close to it as possible. When instructions [or notices] are absolutely necessary, cut them back to a bare minimum.”
― Steve Krug, Don’t Make Me Think: A Common Sense Approach to Web Usability

1 Comment

  1. If I enter an invalid password I see “Invalid password.” show just above the form. Hardly “zilch”.

    You can style this by simple CSS:

    .post-password-form-invalid-password p {
    color:#f00;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.