As a professional WordPress developer, I’ve implemented countless content protection solutions for clients across various industries. Password protection is one of the most versatile and powerful ways to restrict access to sensitive content on your WordPress site. Whether you’re creating client portals, exclusive member areas, or simply hiding work-in-progress pages, understanding how to properly implement password protection is essential.
In this ultimate guide, I’ll walk you through everything you need to know about WordPress password protection – from built-in options to advanced custom solutions that provide robust security while maintaining a seamless user experience.
Before diving into implementation methods, let’s understand what WordPress password protection is and how it works at a fundamental level.
Password protection in WordPress is a content restriction mechanism that requires users to enter a password before they can access specific pages or posts. Unlike membership systems that require user registration, password protection is simpler – anyone with the password can access the content, regardless of whether they have an account on your site.
WordPress includes a built-in password protection feature that works through the post/page visibility settings:
The native system is straightforward but has limitations, which we’ll explore later in this article.

Let’s explore the various approaches to implementing password protection on your WordPress site, starting with the simplest built-in method and progressing to more advanced custom solutions.
The easiest way to password protect content is using WordPress’s built-in functionality:
When visitors try to access this page, they’ll see a password form instead of the actual content. Once they enter the correct password, they’ll gain access to the page for the duration of their browser session.
For basic protection needs, the native method works well. However, for more advanced requirements, you’ll need to explore other options.
For more flexibility and features, WordPress password protection plugins offer enhanced functionality:
1. Password Protected
This lightweight plugin allows you to password protect your entire WordPress site with a single password, while allowing access to your login and admin pages.
Key Features:
Implementation:
2. PPW Pro (Password Protected WordPress Pro)
A more comprehensive solution that offers granular control over password protection.
Key Features:
Implementation:
3. Paid Member Subscriptions
While primarily a membership plugin, it offers excellent content restriction options including password protection.
Key Features:
Implementation:
When selecting a password protection plugin, consider:
For most websites, a dedicated password protection plugin offers the best balance of functionality and ease of use.
For developers or those comfortable with code, custom solutions offer maximum flexibility and control:
// Add this to your theme's functions.php or a custom plugin
function custom_password_protection() {
// Only run on the frontend
if (is_admin()) {
return;
}
// Pages to protect (add your page IDs here)
$protected_pages = array(123, 456, 789);
// Check if we're on a protected page
if (is_page($protected_pages)) {
// The password you want to use
$correct_password = 'your-secure-password';
// Check if the password has been submitted
if (isset($_POST['custom_password_submit'])) {
$submitted_password = sanitize_text_field($_POST['custom_password']);
// Verify the password
if ($submitted_password === $correct_password) {
// Set a cookie that expires in 1 day
setcookie('custom_page_access', md5($correct_password), time() + 86400, COOKIEPATH, COOKIE_DOMAIN);
// Redirect to the same page to avoid form resubmission
wp_redirect(get_permalink());
exit;
} else {
// Wrong password, set an error flag
$error = true;
}
}
// Check if the access cookie exists and is valid
if (!isset($_COOKIE['custom_page_access']) || $_COOKIE['custom_page_access'] !== md5($correct_password)) {
// Display password form and stop page content from loading
?>
<div class="custom-password-form">
<h2>This content is password protected</h2>
<?php if (isset($error) && $error) : ?>
<p class="error">Incorrect password. Please try again.</p>
<?php endif; ?>
<form method="post" action="">
<label for="custom_password">Password:</label>
<input type="password" name="custom_password" id="custom_password" required>
<input type="submit" name="custom_password_submit" value="Access Content">
</form>
</div>
<style>
.custom-password-form {
max-width: 500px;
margin: 50px auto;
padding: 20px;
background: #f9f9f9;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.custom-password-form h2 {
margin-top: 0;
}
.custom-password-form .error {
color: #d63638;
}
.custom-password-form form {
display: flex;
flex-direction: column;
}
.custom-password-form input[type="password"] {
margin: 10px 0;
padding: 8px;
}
.custom-password-form input[type="submit"] {
background: #2271b1;
color: white;
border: none;
padding: 10px;
cursor: pointer;
}
</style>
<?php
exit; // Stop the rest of the page from loading
}
}
}
add_action('template_redirect', 'custom_password_protection');
This custom code solution gives you complete control over the password protection process, including:
For more sophisticated needs, you can extend the basic code to include:
// Enhanced custom password protection with multiple passwords and expiration tracking
function advanced_password_protection() {
// Only run on the frontend
if (is_admin()) {
return;
}
// Configuration - pages and their passwords
$protected_content = array(
123 => array(
'passwords' => array(
'client-password-1' => array(
'expires' => '2025-12-31',
'allowed_ips' => array('192.168.1.1', '10.0.0.1'),
),
'client-password-2' => array(
'expires' => '2025-06-30',
'allowed_ips' => array(),
),
),
'access_log' => true,
),
456 => array(
'passwords' => array(
'team-access-2025' => array(
'expires' => '2025-12-31',
'allowed_ips' => array(),
),
),
'access_log' => true,
),
);
// Get current page ID
$current_page_id = get_queried_object_id();
// Check if current page is protected
if (isset($protected_content[$current_page_id])) {
$page_config = $protected_content[$current_page_id];
$valid_passwords = $page_config['passwords'];
// Process password submission
if (isset($_POST['advanced_password_submit'])) {
$submitted_password = sanitize_text_field($_POST['advanced_password']);
// Check if submitted password is valid
if (array_key_exists($submitted_password, $valid_passwords)) {
$password_config = $valid_passwords[$submitted_password];
// Check password expiration
if (!empty($password_config['expires']) && strtotime($password_config['expires']) < time()) {
$error = 'This password has expired.';
}
// Check IP restriction
elseif (!empty($password_config['allowed_ips']) && !in_array($_SERVER['REMOTE_ADDR'], $password_config['allowed_ips'])) {
$error = 'Access not allowed from your IP address.';
}
else {
// Valid password, set access cookie
$cookie_value = array(
'page_id' => $current_page_id,
'timestamp' => time(),
'password' => $submitted_password
);
setcookie(
'advanced_page_access',
base64_encode(json_encode($cookie_value)),
time() + 86400,
COOKIEPATH,
COOKIE_DOMAIN,
is_ssl(), // Secure cookie if using HTTPS
true // HttpOnly flag
);
// Log access if enabled
if ($page_config['access_log']) {
log_password_access($current_page_id, $submitted_password, $_SERVER['REMOTE_ADDR']);
}
// Redirect to avoid form resubmission
wp_redirect(get_permalink($current_page_id));
exit;
}
} else {
$error = 'Incorrect password. Please try again.';
}
}
// Verify existing cookie
$has_access = false;
if (isset($_COOKIE['advanced_page_access'])) {
$cookie_data = json_decode(base64_decode($_COOKIE['advanced_page_access']), true);
if (
is_array($cookie_data) &&
isset($cookie_data['page_id']) &&
$cookie_data['page_id'] == $current_page_id &&
isset($cookie_data['password']) &&
array_key_exists($cookie_data['password'], $valid_passwords)
) {
$password_config = $valid_passwords[$cookie_data['password']];
// Recheck expiration and IP restrictions
if (
(empty($password_config['expires']) || strtotime($password_config['expires']) >= time()) &&
(empty($password_config['allowed_ips']) || in_array($_SERVER['REMOTE_ADDR'], $password_config['allowed_ips']))
) {
$has_access = true;
}
}
}
// Show password form if no access
if (!$has_access) {
display_advanced_password_form($current_page_id, isset($error) ? $error : null);
exit; // Stop page content from loading
}
}
}
add_action('template_redirect', 'advanced_password_protection');
// Function to log password access
function log_password_access($page_id, $password, $ip) {
$access_log = get_option('password_access_log', array());
$access_log[] = array(
'page_id' => $page_id,
'password' => $password,
'ip' => $ip,
'timestamp' => current_time('mysql'),
'user_agent' => $_SERVER['HTTP_USER_AGENT']
);
// Keep only the last 1000 entries
if (count($access_log) > 1000) {
$access_log = array_slice($access_log, -1000);
}
update_option('password_access_log', $access_log);
}
// Function to display the password form
function display_advanced_password_form($page_id, $error = null) {
?>
<div class="advanced-password-form">
<h2>Protected Content</h2>
<p>Please enter the password to access this content.</p>
<?php if ($error) : ?>
<div class="error-message"><?php echo esc_html($error); ?></div>
<?php endif; ?>
<form method="post" action="">
<div class="form-group">
<label for="advanced_password">Password:</label>
<input type="password" name="advanced_password" id="advanced_password" required>
</div>
<div class="form-actions">
<input type="submit" name="advanced_password_submit" value="Access Content">
</div>
</form>
</div>
<style>
.advanced-password-form {
max-width: 500px;
margin: 50px auto;
padding: 30px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.advanced-password-form h2 {
margin-top: 0;
color: #333;
font-size: 24px;
}
.error-message {
background: #ffebee;
color: #d32f2f;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-group input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.form-actions input[type="submit"] {
background: #2271b1;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.form-actions input[type="submit"]:hover {
background: #135e96;
}
</style>
<?php
}
This advanced implementation includes:
For the most comprehensive content protection, especially if you need user management, WordPress membership plugins provide robust solutions:
1. MemberPress
A premium solution for creating membership sites with advanced content restriction.
Key Features:
2. Restrict Content Pro
A flexible membership plugin with excellent content restriction options.
Key Features:
3. WooCommerce Memberships
If you’re already using WooCommerce for your online store, this extension provides powerful membership features.
Key Features:
This approach combines the benefits of membership management with password protection, giving you both user tracking and simple access for non-members who have the password.

Beyond basic implementation, these advanced strategies can enhance your password protection system:
A client portal allows you to share documents, project updates, and resources with clients in a secure environment:
// Create a client portal with password protection
// Step 1: Register a custom post type for client projects
function register_client_project_post_type() {
$args = array(
'public' => true,
'label' => 'Client Projects',
'menu_icon' => 'dashicons-portfolio',
'supports' => array('title', 'editor', 'thumbnail', 'custom-fields'),
'show_in_menu' => true,
'has_archive' => false,
'publicly_queryable' => true,
'capability_type' => 'post',
'map_meta_cap' => true,
);
register_post_type('client_project', $args);
}
add_action('init', 'register_client_project_post_type');
// Step 2: Add client information metabox
function add_client_project_meta_box() {
add_meta_box(
'client_project_meta',
'Client Information',
'client_project_meta_callback',
'client_project',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'add_client_project_meta_box');
// Step 3: Metabox callback function
function client_project_meta_callback($post) {
wp_nonce_field('client_project_save', 'client_project_nonce');
// Get saved values
$client_name = get_post_meta($post->ID, '_client_name', true);
$client_email = get_post_meta($post->ID, '_client_email', true);
$project_password = get_post_meta($post->ID, '_project_password', true);
$password_expires = get_post_meta($post->ID, '_password_expires', true);
// Generate a random password if empty
if (empty($project_password)) {
$project_password = wp_generate_password(12, false);
}
?>
<p>
<label for="client_name">Client Name:</label>
<input type="text" id="client_name" name="client_name" value="<?php echo esc_attr($client_name); ?>" class="widefat">
</p>
<p>
<label for="client_email">Client Email:</label>
<input type="email" id="client_email" name="client_email" value="<?php echo esc_attr($client_email); ?>" class="widefat">
</p>
<p>
<label for="project_password">Project Password:</label>
<input type="text" id="project_password" name="project_password" value="<?php echo esc_attr($project_password); ?>" class="widefat">
</p>
<p>
<label for="password_expires">Password Expiration Date:</label>
<input type="date" id="password_expires" name="password_expires" value="<?php echo esc_attr($password_expires); ?>" class="widefat">
</p>
<?php
}
// Step 4: Save client information
function save_client_project_meta($post_id) {
// Check nonce
if (!isset($_POST['client_project_nonce']) || !wp_verify_nonce($_POST['client_project_nonce'], 'client_project_save')) {
return;
}
// Check autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Check permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Save client information
if (isset($_POST['client_name'])) {
update_post_meta($post_id, '_client_name', sanitize_text_field($_POST['client_name']));
}
if (isset($_POST['client_email'])) {
update_post_meta($post_id, '_client_email', sanitize_email($_POST['client_email']));
}
if (isset($_POST['project_password'])) {
update_post_meta($post_id, '_project_password', sanitize_text_field($_POST['project_password']));
}
if (isset($_POST['password_expires'])) {
update_post_meta($post_id, '_password_expires', sanitize_text_field($_POST['password_expires']));
}
}
add_action('save_post_client_project', 'save_client_project_meta');
// Step 5: Protect client project pages
function protect_client_project_pages() {
if (is_singular('client_project')) {
$post_id = get_the_ID();
$project_password = get_post_meta($post_id, '_project_password', true);
$password_expires = get_post_meta($post_id, '_password_expires', true);
// Skip protection if no password set
if (empty($project_password)) {
return;
}
// Check if password has expired
$is_expired = false;
if (!empty($password_expires) && strtotime($password_expires) < time()) {
$is_expired = true;
}
// Process password submission
if (isset($_POST['client_project_password_submit'])) {
$submitted_password = sanitize_text_field($_POST['client_project_password']);
if ($is_expired) {
$error = 'This project access has expired.';
}
elseif ($submitted_password === $project_password) {
// Set access cookie (expires in 7 days)
setcookie(
'client_project_access_' . $post_id,
md5($project_password),
time() + (7 * 24 * 60 * 60),
COOKIEPATH,
COOKIE_DOMAIN,
is_ssl(),
true
);
// Log access
log_client_project_access($post_id);
// Redirect to avoid form resubmission
wp_redirect(get_permalink($post_id));
exit;
} else {
$error = 'Incorrect password. Please try again.';
}
}
// Check for valid access cookie
$cookie_name = 'client_project_access_' . $post_id;
if (
!isset($_COOKIE[$cookie_name]) ||
$_COOKIE[$cookie_name] !== md5($project_password) ||
$is_expired
) {
// Display password form
display_client_project_password_form($post_id, isset($error) ? $error : null, $is_expired);
exit;
}
}
}
add_action('template_redirect', 'protect_client_project_pages');
// Step 6: Display password form
function display_client_project_password_form($post_id, $error = null, $is_expired = false) {
$client_name = get_post_meta($post_id, '_client_name', true);
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo get_the_title($post_id); ?> - Client Portal</title>
<?php wp_head(); ?>
<style>
body {
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.client-portal-login {
max-width: 500px;
margin: 100px auto;
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}
.site-logo {
text-align: center;
margin-bottom: 30px;
}
.site-logo img {
max-width: 200px;
height: auto;
}
.client-portal-login h1 {
margin-top: 0;
font-size: 24px;
color: #333;
text-align: center;
}
.client-welcome {
margin-bottom: 20px;
text-align: center;
font-size: 16px;
color: #555;
}
.error-message {
background: #ffebee;
color: #d32f2f;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.expired-message {
background: #fff8e1;
color: #ff8f00;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.form-actions {
text-align: center;
}
.form-actions input[type="submit"] {
background: #2271b1;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.form-actions input[type="submit"]:hover {
background: #135e96;
}
.back-to-home {
text-align: center;
margin-top: 20px;
}
.back-to-home a {
color: #2271b1;
text-decoration: none;
font-size: 14px;
}
</style>
</head>
<body>
<div class="client-portal-login">
<div class="site-logo">
<?php
if (has_custom_logo()) {
the_custom_logo();
} else {
echo '<h2>' . get_bloginfo('name') . '</h2>';
}
?>
</div>
<h1>Client Project Portal</h1>
<?php if (!empty($client_name)) : ?>
<div class="client-welcome">
Welcome, <?php echo esc_html($client_name); ?>
</div>
<?php endif; ?>
<?php if ($is_expired) : ?>
<div class="expired-message">
This project access has expired. Please contact us for assistance.
</div>
<?php else : ?>
<?php if ($error) : ?>
<div class="error-message"><?php echo esc_html($error); ?></div>
<?php endif; ?>
<form method="post" action="">
<div class="form-group">
<label for="client_project_password">Project Password:</label>
<input type="password" name="client_project_password" id="client_project_password" required>
</div>
<div class="form-actions">
<input type="submit" name="client_project_password_submit" value="Access Project">
</div>
</form>
<?php endif; ?>
<div class="back-to-home">
<a href="<?php echo esc_url(home_url('/')); ?>">← Back to Home</a>
</div>
</div>
</body>
</html>
<?php
wp_footer();
exit;
}
// Step 7: Log client project access
function log_client_project_access($post_id) {
$access_logs = get_post_meta($post_id, '_access_logs', true);
if (!is_array($access_logs)) {
$access_logs = array();
}
$access_logs[] = array(
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'timestamp' => current_time('mysql'),
);
// Keep only the last 100 entries
if (count($access_logs) > 100) {
$access_logs = array_slice($access_logs, -100);
}
update_post_meta($post_id, '_access_logs', $access_logs);
}
This comprehensive client portal implementation includes:
You can implement a tiered access system where different passwords provide access to different levels of content:
// Tiered password protection system
function tiered_password_protection() {
// Skip on admin pages
if (is_admin()) {
return;
}
// Define access tiers and their passwords
$access_tiers = array(
'bronze' => array(
'password' => 'bronze2025access',
'pages' => array(101, 102, 103), // Basic content page IDs
),
'silver' => array(
'password' => 'silver2025access',
'pages' => array(101, 102, 103, 201, 202, 203), // Basic + intermediate content
),
'gold' => array(
'password' => 'gold2025access',
'pages' => array(101, 102, 103, 201, 202, 203, 301, 302, 303), // All content
),
);
// Check if we're on a page that needs protection
$current_page_id = get_queried_object_id();
$requires_protection = false;
$required_tier = null;
// Determine which tier is needed for this page
foreach ($access_tiers as $tier => $config) {
if (in_array($current_page_id, $config['pages'])) {
$requires_protection = true;
// If we already have access to this tier, we're good
if (isset($_COOKIE['tiered_access']) && $_COOKIE['tiered_access'] === md5($tier . $config['password'])) {
return; // User has access to this tier
}
// Check if user has access to a higher tier
foreach ($access_tiers as $check_tier => $check_config) {
if (
isset($_COOKIE['tiered_access']) &&
$_COOKIE['tiered_access'] === md5($check_tier . $check_config['password']) &&
in_array($current_page_id, $check_config['pages'])
) {
return; // User has access through a different tier
}
}
// If we're still here, user needs access to this tier
$required_tier = $tier;
break;
}
}
// If page doesn't need protection, exit
if (!$requires_protection) {
return;
}
// Process password submission
if (isset($_POST['tiered_password_submit'])) {
$submitted_password = sanitize_text_field($_POST['tiered_password']);
$submitted_tier = isset($_POST['access_tier']) ? sanitize_text_field($_POST['access_tier']) : '';
// Verify the password for the submitted tier
if (
!empty($submitted_tier) &&
isset($access_tiers[$submitted_tier]) &&
$submitted_password === $access_tiers[$submitted_tier]['password']
) {
// Set access cookie (expires in 30 days)
setcookie(
'tiered_access',
md5($submitted_tier . $submitted_password),
time() + (30 * DAY_IN_SECONDS),
COOKIEPATH,
COOKIE_DOMAIN,
is_ssl(),
true
);
// Log access
log_tiered_access($submitted_tier, $current_page_id);
// Redirect to the same page
wp_redirect(get_permalink($current_page_id));
exit;
} else {
$error = 'Incorrect password or access tier. Please try again.';
}
}
// Display password form for the required tier
display_tiered_password_form($required_tier, $current_page_id, isset($error) ? $error : null);
exit;
}
add_action('template_redirect', 'tiered_password_protection');
// Display tiered password form
function display_tiered_password_form($required_tier, $page_id, $error = null) {
// Get available tiers
$tiers = array(
'bronze' => 'Bronze Access (Basic Content)',
'silver' => 'Silver Access (Intermediate Content)',
'gold' => 'Gold Access (Premium Content)',
);
// Get page information
$page_title = get_the_title($page_id);
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Access Required - <?php echo esc_html($page_title); ?></title>
<?php wp_head(); ?>
<style>
body {
background-color: #f7f7f7;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
line-height: 1.6;
}
.tiered-access-container {
max-width: 600px;
margin: 80px auto;
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.site-branding {
text-align: center;
margin-bottom: 30px;
}
.site-branding img {
max-width: 180px;
height: auto;
}
h1 {
text-align: center;
color: #333;
font-size: 28px;
margin-top: 0;
margin-bottom: 10px;
}
.page-info {
text-align: center;
margin-bottom: 30px;
color: #666;
}
.access-level-required {
background: #e8f5e9;
padding: 15px;
border-radius: 5px;
text-align: center;
margin-bottom: 25px;
border-left: 4px solid #4caf50;
}
.access-level-required h3 {
margin-top: 0;
margin-bottom: 5px;
color: #2e7d32;
}
.error-message {
background: #ffebee;
color: #c62828;
padding: 15px;
border-radius: 5px;
margin-bottom: 25px;
text-align: center;
border-left: 4px solid #ef5350;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-group select,
.form-group input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.form-actions {
text-align: center;
}
.form-actions button {
background: #2271b1;
color: white;
border: none;
padding: 14px 28px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
}
.form-actions button:hover {
background: #135e96;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.back-link {
text-align: center;
margin-top: 25px;
}
.back-link a {
color: #2271b1;
text-decoration: none;
}
.tier-description {
font-size: 14px;
color: #666;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="tiered-access-container">
<div class="site-branding">
<?php
if (has_custom_logo()) {
the_custom_logo();
} else {
echo '<h2>' . get_bloginfo('name') . '</h2>';
}
?>
</div>
<h1>Protected Content</h1>
<div class="page-info">
You're trying to access: <strong><?php echo esc_html($page_title); ?></strong>
</div>
<div class="access-level-required">
<h3>Access Level Required</h3>
<p><?php echo esc_html($tiers[$required_tier]); ?> or higher</p>
</div>
<?php if ($error) : ?>
<div class="error-message"><?php echo esc_html($error); ?></div>
<?php endif; ?>
<form method="post" action="">
<div class="form-group">
<label for="access_tier">Select Your Access Tier:</label>
<select name="access_tier" id="access_tier">
<?php foreach ($tiers as $tier_id => $tier_name) : ?>
<option value="<?php echo esc_attr($tier_id); ?>" <?php selected($tier_id, $required_tier); ?>>
<?php echo esc_html($tier_name); ?>
</option>
<?php endforeach; ?>
</select>
<div class="tier-description">
Select the access tier that matches your password.
</div>
</div>
<div class="form-group">
<label for="tiered_password">Password:</label>
<input type="password" name="tiered_password" id="tiered_password" required>
</div>
<div class="form-actions">
<button type="submit" name="tiered_password_submit">Access Content</button>
</div>
</form>
<div class="back-link">
<a href="<?php echo esc_url(home_url('/')); ?>">← Return to Homepage</a>
</div>
</div>
</body>
</html>
<?php
wp_footer();
exit;
}
// Log tiered access
function log_tiered_access($tier, $page_id) {
$access_logs = get_option('tiered_access_logs', array());
$access_logs[] = array(
'tier' => $tier,
'page_id' => $page_id,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'timestamp' => current_time('mysql'),
);
// Keep only the last 1000 entries
if (count($access_logs) > 1000) {
$access_logs = array_slice($access_logs, -1000);
}
update_option('tiered_access_logs', $access_logs);
}
This tiered access system allows you to:

For situations where you want to grant temporary access:
// Time-limited password protection
function generate_timed_access_code($hours = 24, $for_page = null) {
// Generate a unique access code
$access_code = wp_generate_password(8, false);
// Store the code with expiration time
$timed_codes = get_option('timed_access_codes', array());
$timed_codes[$access_code] = array(
'expires' => time() + ($hours * 3600),
'page_id' => $for_page,
'created' => current_time('mysql'),
);
update_option('timed_access_codes', $timed_codes);
return $access_code;
}
// Example usage in admin:
// $access_code = generate_timed_access_code(48, 123); // 48-hour access to page ID 123
function timed_password_protection() {
// Skip on admin pages
if (is_admin()) {
return;
}
// Get protected pages configuration
$protected_pages = array(
123 => 'Project Proposal',
456 => 'Client Dashboard',
789 => 'Financial Report',
);
// Check if we're on a protected page
$current_page_id = get_queried_object_id();
if (!isset($protected_pages[$current_page_id])) {
return; // Not a protected page
}
// Process access code submission
if (isset($_POST['timed_access_submit'])) {
$submitted_code = sanitize_text_field($_POST['timed_access_code']);
// Get stored access codes
$timed_codes = get_option('timed_access_codes', array());
// Check if the code exists and is valid
if (
isset($timed_codes[$submitted_code]) &&
(
$timed_codes[$submitted_code]['page_id'] === null ||
$timed_codes[$submitted_code]['page_id'] == $current_page_id
) &&
$timed_codes[$submitted_code]['expires'] > time()
) {
// Valid code, set access cookie
setcookie(
'timed_page_access_' . $current_page_id,
md5($submitted_code . $current_page_id),
$timed_codes[$submitted_code]['expires'],
COOKIEPATH,
COOKIE_DOMAIN,
is_ssl(),
true
);
// Log access
log_timed_access($current_page_id, $submitted_code);
// Redirect to avoid form resubmission
wp_redirect(get_permalink($current_page_id));
exit;
} else {
if (isset($timed_codes[$submitted_code]) && $timed_codes[$submitted_code]['expires'] <= time()) {
$error = 'This access code has expired.';
} else {
$error = 'Invalid access code. Please try again or request a new code.';
}
}
}
// Check if user has valid access cookie
$cookie_name = 'timed_page_access_' . $current_page_id;
$has_access = false;
if (isset($_COOKIE[$cookie_name])) {
// Verify cookie against stored codes
$timed_codes = get_option('timed_access_codes', array());
foreach ($timed_codes as $code => $data) {
if (
$_COOKIE[$cookie_name] === md5($code . $current_page_id) &&
$data['expires'] > time() &&
($data['page_id'] === null || $data['page_id'] == $current_page_id)
) {
$has_access = true;
break;
}
}
}
// If no access, show the access form
if (!$has_access) {
display_timed_access_form($current_page_id, $protected_pages[$current_page_id], isset($error) ? $error : null);
exit;
}
}
add_action('template_redirect', 'timed_password_protection');
// Display timed access form
function display_timed_access_form($page_id, $page_name, $error = null) {
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Timed Access Required - <?php echo esc_html($page_name); ?></title>
<?php wp_head(); ?>
<style>
body {
background-color: #f8f9fa;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.timed-access-container {
max-width: 550px;
margin: 100px auto;
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
text-align: center;
}
.page-icon {
font-size: 48px;
color: #2271b1;
margin-bottom: 20px;
}
h1 {
color: #333;
font-size: 24px;
margin-top: 0;
margin-bottom: 10px;
}
.page-name {
font-size: 18px;
color: #555;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.access-instructions {
margin-bottom: 25px;
text-align: left;
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
font-size: 15px;
color: #555;
}
.error-message {
background: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 5px;
margin-bottom: 25px;
font-size: 15px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 10px;
font-weight: 500;
text-align: left;
}
.form-group input[type="text"] {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
box-sizing: border-box;
text-align: center;
letter-spacing: 2px;
font-weight: 600;
}
.form-actions button {
background: #2271b1;
color: white;
border: none;
padding: 14px 28px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
width: 100%;
}
.form-actions button:hover {
background: #135e96;
}
.help-text {
margin-top: 25px;
font-size: 14px;
color: #666;
}
.help-text a {
color: #2271b1;
text-decoration: none;
}
</style>
</head>
<body>
<div class="timed-access-container">
<div class="page-icon">🔒</div>
<h1>Timed Access Required</h1>
<div class="page-name">
<?php echo esc_html($page_name); ?>
</div>
<div class="access-instructions">
<strong>Instructions:</strong> Enter the access code that was provided to you. This code grants temporary access to this content and will expire after the designated time period.
</div>
<?php if ($error) : ?>
<div class="error-message"><?php echo esc_html($error); ?></div>
<?php endif; ?>
<form method="post" action="">
<div class="form-group">
<label for="timed_access_code">Enter Access Code:</label>
<input type="text" name="timed_access_code" id="timed_access_code" placeholder="XXXXXXXX" maxlength="8" required>
</div>
<div class="form-actions">
<button type="submit" name="timed_access_submit">Access Content</button>
</div>
</form>
<div class="help-text">
Need an access code? <a href="<?php echo esc_url(home_url('/contact/')); ?>">Contact us</a> to request one.
</div>
</div>
</body>
</html>
<?php
wp_footer();
exit;
}
// Log timed access
function log_timed_access($page_id, $access_code) {
$access_logs = get_option('timed_access_logs', array());
$access_logs[] = array(
'page_id' => $page_id,
'code' => $access_code,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'timestamp' => current_time('mysql'),
);
// Keep only the last 1000 entries
if (count($access_logs) > 1000) {
$access_logs = array_slice($access_logs, -1000);
}
update_option('timed_access_logs', $access_logs);
}
// Admin function to view and manage timed access codes
function timed_access_admin_page() {
// Implementation of admin interface for managing codes
// (This would typically be added to the WordPress admin menu)
}
This time-limited access system allows you to:
Password protection has important implications for SEO that you should consider:
For password-protected pages that shouldn’t be indexed:
function add_noindex_to_protected_pages() {
if (post_password_required()) {
echo '<meta name="robots" content="noindex, nofollow" />';
}
}
add_action('wp_head', 'add_noindex_to_protected_pages');
This prevents search engines from indexing password-protected content, which is usually desirable since users finding these pages in search results would be frustrated by the password requirement.
For pages that should be discoverable but have protected content:
function custom_password_form_with_preview($output) {
global $post;
// Only modify the password form on specific pages
if (is_singular() && in_array($post->ID, array(123, 456, 789))) {
// Create custom preview content
$preview_content = '<div class="content-preview">';
$preview_content .= '<h3>Content Preview</h3>';
$preview_content .= '<p>This premium content covers advanced techniques for WordPress optimization, including:</p>';
$preview_content .= '<ul>';
$preview_content .= '<li>Database optimization strategies</li>';
$preview_content .= '<li>Advanced caching configurations</li>';
$preview_content .= '<li>Server-level performance tuning</li>';
$preview_content .= '</ul>';
$preview_content .= '<p><strong>Access the full content by entering the password below.</strong></p>';
$preview_content .= '</div>';
// Add preview content before the password form
$output = $preview_content . $output;
}
return $output;
}
add_filter('the_password_form', 'custom_password_form_with_preview');
This approach allows you to show a preview of the protected content to both users and search engines, providing context while still protecting the full content.
Add schema markup to indicate content requirements:
function add_schema_for_protected_content() {
if (post_password_required()) {
?>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "<?php echo esc_js(get_the_title()); ?>",
"description": "Protected content requiring password access",
"accessMode": "textual",
"accessModeSufficient": "textual",
"accessibilityControl": "fullKeyboardControl",
"accessibilityFeature": "passwordProtected"
}
</script>
<?php
}
}
add_action('wp_head', 'add_schema_for_protected_content');
This structured data helps search engines understand that the content requires authentication.
Password protection implementations can impact both site performance and security. Here are some best practices:
// Cache-friendly password protection
function cache_friendly_password_protection() {
// Only check on protected pages
if (!is_singular() || !post_password_required()) {
return;
}
// Add no-cache headers for protected pages
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
// Exclude protected pages from caching plugins
if (!defined('DONOTCACHEPAGE')) {
define('DONOTCACHEPAGE', true);
}
}
add_action('template_redirect', 'cache_friendly_password_protection', 5);
This ensures password-protected pages aren’t cached, which could lead to security issues or functionality problems.
For more comprehensive performance optimization, check out my guide on WordPress page speed optimization.
// Security enhancements for password protection
// 1. Limit password attempts
function limit_password_attempts() {
if (!isset($_POST['post_password'])) {
return;
}
$ip = $_SERVER['REMOTE_ADDR'];
$attempts = get_transient('password_attempts_' . $ip);
if ($attempts === false) {
$attempts = 1;
} else {
$attempts++;
}
// Store attempt count (expires after 1 hour)
set_transient('password_attempts_' . $ip, $attempts, HOUR_IN_SECONDS);
// Block after 5 failed attempts
if ($attempts > 5) {
wp_die(
'Too many password attempts. Please try again later.',
'Security Notice',
array('response' => 403)
);
}
}
add_action('init', 'limit_password_attempts');
// 2. Use stronger password cookies
function strengthen_password_cookies($cookie_name, $post_id) {
// Add current time to make each cookie unique
return $cookie_name . '-' . md5(time() . $post_id);
}
add_filter('post_password_cookie', 'strengthen_password_cookies', 10, 2);
// 3. Implement HTTPS requirement for protected pages
function require_https_for_protected_pages() {
if (post_password_required() && !is_ssl()) {
wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 301);
exit;
}
}
add_action('template_redirect', 'require_https_for_protected_pages');
These security enhancements help protect your password-protected content from brute force attacks and other security vulnerabilities. For a more comprehensive approach, see my guide on WordPress security best practices.
Password protection is a versatile tool in your WordPress security arsenal, but choosing the right implementation depends on your specific needs:
By carefully considering your specific requirements, you can implement the ideal password protection solution that balances security, usability, and performance.
Remember that password protection is just one aspect of a comprehensive content restriction strategy. For more advanced needs, consider exploring full membership solutions like those covered in my guide on how to create a membership site with WordPress.
If you need help implementing custom password protection for your WordPress site, consider working with a WordPress expert who can create a tailored solution that perfectly matches your requirements.
By following the approaches outlined in this guide, you can create secure, user-friendly password protection for your WordPress content that keeps your sensitive information safe while providing a seamless experience for authorized users.
Jackober is a seasoned WordPress expert and digital strategist with a passion for empowering website owners. With years of hands-on experience in web development, SEO, and online security, Jackober delivers reliable, practical insights to help you build, secure, and optimize your WordPress site with ease.