📰

Wordpress

67 notes  •  WordPress & CMS

Fix Visual/Text Editor Tabs Missing in WordPress

If the Visual and Text editor tabs have disappeared from the WordPress page or post editor, several quick fixes can restore them without reinstalling WordPress.

Prerequisites

  • WordPress admin access
  • Ability to install plugins or upload files via FTP

Steps

  1. Toggle the user profile setting. Go to Users → Your Profile and locate the Visual Editor checkbox. Uncheck it, save, then re-check it and save again. This resets the editor preference.
  2. Install the "Use Google Libraries" plugin if the profile toggle does not help. This plugin offloads common JavaScript libraries (including TinyMCE dependencies) to Google's CDN, often resolving conflicts.
  3. Replace core WordPress files. If the issue persists, download a fresh copy of WordPress matching your version, then upload the wp-admin/js/ folder via FTP to overwrite corrupted files.

Verify

Open any page or post in the editor. Both Visual and Text tabs should appear at the top-right of the editor toolbar.

Notes

  • Conflicts with certain plugins (especially those that modify TinyMCE or enqueue extra JavaScript) can cause tabs to vanish. Try disabling plugins one by one to isolate the culprit.
  • Ensure your browser is not blocking scripts from the WordPress domain.

Customize the WordPress Admin for Clients

When building sites for non-technical clients, customizing the WordPress admin area makes it easier for them to manage content and reduces support overhead. This guide covers the most effective approaches — both code-based and plugin-based.

Prerequisites

  • WordPress admin access
  • A child theme or site-specific plugin for custom code

Steps

  1. Brand the login screen. Add a custom logo and background to the login page using the login_enqueue_scripts, login_headerurl, and login_headertitle hooks in your plugin or functions.php.
  2. Remove unused dashboard widgets. Use wp_dashboard_setup to call remove_meta_box() for widgets clients don't need (e.g., news feeds, quick drafts).
  3. Restrict admin menu items. Use remove_menu_page() and remove_submenu_page() inside an admin_menu action to hide sections irrelevant to the client.
  4. Add a custom welcome widget. Register a new dashboard widget via wp_add_dashboard_widget() with instructions specific to the client's site.
  5. Use a plugin for comprehensive rebranding. Plugins such as Ultimate Branding or White Label CMS provide a settings UI to control login branding, admin colors, menu items, and dashboard widgets without custom code.

Verify

Log in as a lower-privileged user (Editor or Author) and confirm that only the intended menu items and dashboard widgets are visible.

Notes

  • Place customizations in a site-specific plugin rather than a theme's functions.php so they persist across theme changes.
  • Use current_user_can() checks to apply restrictions only to non-admin roles, keeping full access for administrators.

String Translation with WPML

WPML's String Translation module lets you translate text that does not belong to posts, pages, or taxonomies — such as the site tagline, widget titles, theme strings, and plugin labels.

Prerequisites

  • WPML Multilingual CMS package installed and activated
  • String Translation module enabled in WPML → Modules
  • At least one secondary language configured

Steps

  1. Go to WPML → String Translation in the WordPress admin.
  2. Use the search box or filter by domain (e.g., WordPress, your theme's text domain, or a plugin's text domain) to find the string you want to translate.
  3. Click the + icon next to the string in the target language column.
  4. Enter the translation and click Save.
  5. To scan a theme or plugin for new strings, go to WPML → Theme and Plugin Localization and run a scan.

Verify

Switch the site to the secondary language (via the language switcher or by visiting the language URL prefix) and confirm the translated string appears correctly.

Notes

  • Strings must be wrapped in standard WordPress translation functions (__(), _e(), etc.) to be detected by WPML.
  • Hardcoded strings in templates that bypass translation functions cannot be managed through String Translation.

Fix Product Category URL Text in WPML Second Language

By default, WooCommerce uses product-category as the URL slug for product category archives. When using WPML, this slug may not be translated for secondary languages, resulting in mixed-language URLs. This guide shows how to set the translated slug.

Prerequisites

  • WPML Multilingual CMS with WooCommerce Multilingual installed
  • At least one secondary language active

Steps

  1. Go to WooCommerce → Settings → Advanced → WooCommerce Multilingual (or via the WPML menu).
  2. Find the Custom taxonomy slugs section and locate product_cat.
  3. Enter the translated slug for the secondary language (e.g., categorie-produit for French).
  4. Save changes, then go to Settings → Permalinks and click Save Changes to flush rewrite rules.

Verify

Browse to a product category page in the secondary language. The URL should now use the translated slug instead of product-category.

Notes

  • After any permalink change, always flush rewrite rules by saving the Permalinks settings page.
  • If the WPML + WooCommerce Multilingual combination does not expose a slug UI, you can filter the slug programmatically using the woocommerce_product_cat_rewrite_slug filter.

Create a Two-Column Contact Form 7 Layout

Contact Form 7 outputs forms as a single column by default. Adding CSS wrapper classes to the form markup makes it easy to display fields in a two-column grid.

Prerequisites

  • Contact Form 7 plugin installed and active
  • Access to add CSS (theme stylesheet or the WordPress Customizer)

Steps

1. Update the form markup in CF7

Edit the form in Contact → Contact Forms and wrap field pairs in div elements:

<div class="cf7-row">
  <div class="cf7-col">
    <label>First Name</label>
    [text* first-name]
  </div>
  <div class="cf7-col">
    <label>Last Name</label>
    [text* last-name]
  </div>
</div>
<div class="cf7-row">
  <div class="cf7-col">
    <label>Email</label>
    [email* your-email]
  </div>
  <div class="cf7-col">
    <label>Phone</label>
    [tel your-phone]
  </div>
</div>

2. Add the CSS

.cf7-row {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
}
.cf7-col {
  flex: 1;
}
.cf7-col input,
.cf7-col select,
.cf7-col textarea {
  width: 100%;
}

Verify

Preview the page containing the form. Fields should appear side-by-side in two columns on desktop widths.

Notes

  • For a responsive layout that collapses to a single column on mobile, see the companion guide on responsive two-column CF7 forms.
  • Avoid using tables for layout; the flexbox approach above is accessible and responsive.

Change "Leave a Reply" Text in WordPress

The "Leave a Reply" heading above the WordPress comment form is generated by the comment_form() function. You can override it by passing custom arguments to that function in your theme's comments.php file.

Prerequisites

  • Access to your theme's comments.php file (use a child theme)

Steps

Option A — Pass arguments directly to comment_form()

Locate the comment_form(); call in comments.php and replace it with:

<?php
comment_form( array(
    'title_reply'          => 'Go on, Leave a Reply!',
    'title_reply_to'       => 'Reply to %s',
    'label_submit'         => 'Post Comment',
) );
?>

Option B — Use a filter (no template edit needed)

Add this to your child theme's functions.php or a site plugin:

add_filter( 'comment_form_defaults', function( $defaults ) {
    $defaults['title_reply'] = 'Share Your Thoughts';
    return $defaults;
} );

Verify

Open a single post page. The comment section heading should now display your custom text.

Notes

  • Option B (the filter) is preferred because it works without editing template files and survives theme updates when placed in a child theme or plugin.
  • The title_reply_to argument controls the text shown when a visitor replies to a specific comment (it accepts a %s placeholder for the commenter's name).

Responsive Two-Column Contact Form 7

This guide extends the basic two-column CF7 layout with a responsive breakpoint so the form collapses to a single column on mobile devices.

Prerequisites

  • Contact Form 7 installed and active
  • Access to add CSS to your theme

Steps

1. Form markup

Use the same wrapper approach as the basic two-column form:

<div class="cf7-row">
  <div class="cf7-col">[text* first-name placeholder "First Name"]</div>
  <div class="cf7-col">[text* last-name placeholder "Last Name"]</div>
</div>
<div class="cf7-row">
  <div class="cf7-col">[email* your-email placeholder "Email"]</div>
  <div class="cf7-col">[tel your-phone placeholder "Phone"]</div>
</div>
<div class="cf7-full">
  [textarea your-message placeholder "Message"]
  [submit "Send Message"]
</div>

2. Responsive CSS

.cf7-row {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
}
.cf7-col {
  flex: 1;
}
.cf7-col input,
.cf7-col select {
  width: 100%;
}
.cf7-full textarea,
.cf7-full input[type="submit"] {
  width: 100%;
}

@media (max-width: 600px) {
  .cf7-row {
    flex-direction: column;
    gap: 0;
  }
}

Verify

View the form at desktop width — fields appear in two columns. Resize below 600 px — fields stack vertically.

Notes

  • Adjust the 600px breakpoint to match your theme's mobile breakpoint.
  • Full-width fields (like message and submit) should be placed outside the .cf7-row wrapper.

Use and Register WordPress Menus

WordPress navigation menus must be registered in functions.php before they can be assigned and displayed in templates. This guide covers registration and output.

Prerequisites

  • A WordPress theme (or child theme) with access to functions.php

Steps

1. Register the menu location in functions.php

add_action( 'init', 'mytheme_register_menus' );

function mytheme_register_menus() {
    register_nav_menus( array(
        'primary'  => __( 'Primary Menu', 'mytheme' ),
        'footer'   => __( 'Footer Menu', 'mytheme' ),
    ) );
}

2. Output the menu in a template file

In header.php (or wherever you want the menu):

<?php
wp_nav_menu( array(
    'theme_location' => 'primary',
    'container'      => 'nav',
    'container_class'=> 'main-nav',
    'menu_class'     => 'menu-list',
    'fallback_cb'    => false,
) );
?>

3. Assign a menu to the location

Go to Appearance → Menus, create or select a menu, and assign it to the registered location under Menu Settings → Display location.

Verify

Visit the front end. The menu assigned to the location should render inside a <nav> element.

Notes

  • Always use theme_location rather than menu (by name or ID) so the menu is portable across different setups.
  • The fallback_cb => false argument prevents WordPress from outputting a page list when no menu is assigned.

WordPress Custom Page Templates Guide

Custom page templates let you create unique layouts for specific pages without modifying your theme's default templates. WordPress automatically detects template files in your theme and makes them selectable from the Page editor.

Prerequisites

  • A child theme (recommended) or direct theme access
  • Basic understanding of PHP and the WordPress template hierarchy

Steps

1. Create the template file

In your (child) theme folder, create a new PHP file, for example template-landing.php. Add the template header comment at the top:

<?php
/**
 * Template Name: Landing Page
 * Template Post Type: page
 */
get_header(); ?>

<main>
  <?php while ( have_posts() ) : the_post(); ?>
    <article>
      <h1><?php the_title(); ?></h1>
      <?php the_content(); ?>
    </article>
  <?php endwhile; ?>
</main>

<?php get_footer(); ?>

2. Assign the template to a page

  1. Edit a page in the WordPress admin.
  2. In the Page Attributes panel (or the block editor sidebar), open the Template dropdown.
  3. Select Landing Page and update the page.

Verify

Visit the page on the front end. It should render using your custom template instead of the default page.php.

Notes

  • The Template Name: comment is required — WordPress will not recognize the file without it.
  • The Template Post Type: comment is optional; omit it and the template becomes available for all post types.
  • You can organize templates in subdirectories (e.g., templates/template-landing.php) — WordPress will still detect them.

WordPress functions.php File Guide

The functions.php file is the central place in a WordPress theme to add functionality, register features, enqueue assets, and hook into WordPress actions and filters. This guide explains its role and how to use it safely.

Prerequisites

  • A child theme (strongly recommended — edits to a parent theme's functions.php are overwritten on update)
  • FTP or file manager access as a backup in case of a PHP error

Key Uses

Enqueue scripts and styles

add_action( 'wp_enqueue_scripts', 'mytheme_assets' );

function mytheme_assets() {
    wp_enqueue_style( 'theme-style', get_stylesheet_uri() );
    wp_enqueue_script( 'theme-js', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '1.0', true );
}

Add theme support

add_action( 'after_setup_theme', 'mytheme_setup' );

function mytheme_setup() {
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'title-tag' );
    add_theme_support( 'html5', array( 'search-form', 'comment-form' ) );
}

Register sidebars

add_action( 'widgets_init', 'mytheme_sidebars' );

function mytheme_sidebars() {
    register_sidebar( array(
        'name'          => 'Main Sidebar',
        'id'            => 'sidebar-1',
        'before_widget' => '<div class="widget">',
        'after_widget'  => '</div>',
    ) );
}

Verify

After saving changes, visit the site front end and confirm the added functionality works (scripts load, theme support is active, sidebars appear).

Notes

  • A syntax error in functions.php will produce a white screen. Keep FTP access handy to revert the file.
  • For functionality not specific to the active theme, use a site-specific plugin instead so it persists across theme changes.
  • Always prefix function names with a unique identifier (e.g., mytheme_) to avoid conflicts with WordPress core or plugins.

Create a WordPress Admin User via CLI or PHP

When you cannot log in to the WordPress admin, you can create a new administrator account directly via WP-CLI, a PHP script, or raw SQL.

Prerequisites

  • SSH or database access to the server
  • Knowledge of your database table prefix (default: wp_)

Option A — WP-CLI (recommended)

wp user create newadmin admin@example.com   --role=administrator   --user_pass='StrongPassword123!'

Option B — MySQL

-- Step 1: Insert the user
INSERT INTO `wp_users`
  (`ID`, `user_login`, `user_pass`, `user_nicename`,
   `user_email`, `user_registered`, `user_status`, `display_name`)
VALUES
  (NULL, 'newadmin', MD5('StrongPassword123!'),
   'newadmin', 'admin@example.com',
   NOW(), '0', 'New Admin');

-- Step 2: Grant administrator capabilities
INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`)
VALUES
  (NULL, (SELECT MAX(ID) FROM wp_users), 'wp_capabilities',
   'a:1:{s:13:"administrator";b:1;}'),
  (NULL, (SELECT MAX(ID) FROM wp_users), 'wp_user_level', '10');

Option C — PHP script

Create a temporary file create-admin.php in the WordPress root:

<?php
require_once 'wp-load.php';
$user_id = wp_create_user( 'newadmin', 'StrongPassword123!', 'admin@example.com' );
$user = new WP_User( $user_id );
$user->set_role( 'administrator' );
echo 'Done. Delete this file now.';
?>

Visit the file in your browser, then delete it immediately.

Verify

Log in at /wp-admin with the new credentials. Confirm the user has Administrator role under Users → All Users.

Notes

  • Using MD5() in SQL is less secure than WP-CLI's hashed passwords. Prefer WP-CLI when possible.
  • Delete any temporary PHP scripts immediately after use.
  • Replace wp_ with your actual table prefix if it differs.

Fix WordPress Admin Bar Overlapping with Menu

The WordPress admin bar adds 32 px of height to the top of the page for logged-in users. If your theme's navigation menu is fixed or sticky, the admin bar can overlap it. The fix is to offset the menu's top position via CSS.

Prerequisites

  • Access to your theme's stylesheet or the Customizer's Additional CSS panel

Steps

Option A — CSS offset (simplest)

Add this to your theme's CSS or Customizer → Additional CSS:

/* Push fixed/sticky header down for logged-in users */
.admin-bar .site-header,
.admin-bar .fixed-menu,
.admin-bar nav.sticky {
    top: 32px;
}

@media screen and (max-width: 782px) {
    .admin-bar .site-header,
    .admin-bar .fixed-menu,
    .admin-bar nav.sticky {
        top: 46px; /* admin bar is taller on mobile */
    }
}

Option B — PHP with inline style

Add to functions.php:

add_action( 'wp_head', 'fix_admin_bar_overlap' );

function fix_admin_bar_overlap() {
    if ( is_admin_bar_showing() ) {
        echo '<style>
            .fixed-menu { top: 32px; }
            @media (max-width: 782px) { .fixed-menu { top: 46px; } }
        </style>';
    }
}

Verify

Log in and visit the front end. The navigation menu and admin bar should no longer overlap.

Notes

  • Replace .site-header, .fixed-menu, and nav.sticky with your theme's actual selector for the sticky/fixed element.
  • The admin bar is 32 px on desktop and 46 px on screens narrower than 782 px — always handle both breakpoints.
  • To hide the admin bar entirely for a specific role, use show_admin_bar( false ) inside a conditional check.

Add Google reCAPTCHA to WordPress

Google reCAPTCHA protects WordPress forms from spam and bot submissions. This guide covers adding reCAPTCHA v2 (checkbox) to a custom form or via a plugin for Contact Form 7 or Gravity Forms.

Prerequisites

Option A — Plugin (no code required)

  1. Install Advanced Google reCAPTCHA or use the built-in reCAPTCHA integration in Contact Form 7 (CF7 ≥ 5.1 supports reCAPTCHA v3).
  2. Enter your site key and secret key in the plugin settings.
  3. Enable reCAPTCHA on the desired forms.

Option B — Manual integration

1. Enqueue the reCAPTCHA script

add_action( 'wp_enqueue_scripts', 'enqueue_recaptcha' );

function enqueue_recaptcha() {
    wp_enqueue_script(
        'google-recaptcha',
        'https://www.google.com/recaptcha/api.js',
        array(),
        null,
        true
    );
}

2. Add the widget to your form

<form method="post">
  <!-- your fields -->
  <div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Submit</button>
</form>

3. Verify the response on the server

$response = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', array(
    'body' => array(
        'secret'   => 'YOUR_SECRET_KEY',
        'response' => sanitize_text_field( $_POST['g-recaptcha-response'] ),
    ),
) );
$data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! $data->success ) {
    wp_die( 'reCAPTCHA verification failed.' );
}

Verify

Submit the form without completing the reCAPTCHA — the submission should be rejected. Complete it and resubmit — the form should process normally.

Notes

  • Never expose your secret key in client-side JavaScript.
  • reCAPTCHA v3 is invisible and assigns a score instead of showing a checkbox — prefer it for smoother UX.

Autofill Country Code and Export Emails from WordPress DB

This guide covers two tasks: (1) auto-populating a country dial code field in a WordPress form based on a country dropdown selection, and (2) exporting email addresses from the WordPress database.

Prerequisites

  • WordPress admin access
  • Access to functions.php or a custom plugin for PHP code
  • phpMyAdmin or WP-CLI for DB exports

Part 1 — Autofill Country Code

Add the country dial-code map and JavaScript to your theme:

// functions.php — localize the country code data
add_action( 'wp_enqueue_scripts', 'enqueue_country_code_script' );

function enqueue_country_code_script() {
    wp_enqueue_script( 'country-code', get_stylesheet_directory_uri() . '/js/country-code.js', array( 'jquery' ), '1.0', true );
    $codes = array(
        'AF' => '+93', 'AU' => '+61', 'BD' => '+880',
        'BR' => '+55', 'CA' => '+1',  'CN' => '+86',
        'DE' => '+49', 'FR' => '+33', 'GB' => '+44',
        'IN' => '+91', 'JP' => '+81', 'MX' => '+52',
        'NG' => '+234','PK' => '+92', 'US' => '+1',
        // add more as needed
    );
    wp_localize_script( 'country-code', 'CountryCodes', $codes );
}
// js/country-code.js
jQuery( '#country-select' ).on( 'change', function () {
    var code = CountryCodes[ jQuery( this ).val() ] || '';
    jQuery( '#dial-code' ).val( code );
} );

Part 2 — Export Emails from the Database

Via WP-CLI

wp user list --fields=user_email --format=csv > emails.csv

Via MySQL

SELECT user_email FROM wp_users
INTO OUTFILE '/tmp/emails.csv'
FIELDS TERMINATED BY ','
LINES TERMINATED BY '
';

Verify

Open the exported CSV and confirm email addresses are present and correctly formatted.

Notes

  • Always sanitize and validate form input server-side, regardless of client-side autofill.
  • For GDPR compliance, ensure you have a lawful basis before exporting user email addresses.

Export Custom WordPress DB Query Results to CSV

WordPress's $wpdb object lets you run custom SQL queries. This guide shows how to execute a custom query and force the browser to download the results as a CSV file.

Prerequisites

  • Access to a WordPress plugin or theme file where PHP code can be executed
  • Familiarity with basic SQL

Steps

1. Create a download handler

Add the following to a plugin or a page template. Trigger it via a URL parameter such as ?export_csv=1.

add_action( 'init', 'export_custom_query_to_csv' );

function export_custom_query_to_csv() {
    if ( ! isset( $_GET['export_csv'] ) || ! current_user_can( 'manage_options' ) ) {
        return;
    }

    global $wpdb;

    $results = $wpdb->get_results(
        "SELECT ID, post_title, post_date, post_status
         FROM {$wpdb->posts}
         WHERE post_type = 'post' AND post_status = 'publish'
         ORDER BY post_date DESC",
        ARRAY_A
    );

    if ( empty( $results ) ) {
        wp_die( 'No results found.' );
    }

    $filename = 'export-' . date( 'Y-m-d' ) . '.csv';

    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename=' . $filename );

    $output = fopen( 'php://output', 'w' );

    // Column headers
    fputcsv( $output, array_keys( $results[0] ) );

    // Data rows
    foreach ( $results as $row ) {
        fputcsv( $output, $row );
    }

    fclose( $output );
    exit;
}

2. Trigger the download

While logged in as an administrator, visit: https://example.com/?export_csv=1

Verify

The browser should download a .csv file containing the query results with correct column headers.

Notes

  • Always gate the export behind current_user_can() to prevent unauthorized data access.
  • Use $wpdb->prepare() when incorporating user-supplied values into queries to prevent SQL injection.
  • For large datasets, consider using $wpdb->get_results() with LIMIT and OFFSET pagination to avoid memory exhaustion.

Change Gravity Forms Default Validation Messages

Gravity Forms displays a generic validation error message at the top of the form when submission fails. You can replace this message with a custom one using the gform_validation_message filter.

Prerequisites

  • Gravity Forms plugin installed and active
  • Access to your theme's functions.php or a custom plugin

Steps

Change the message for all forms

add_filter( 'gform_validation_message', 'custom_gf_validation_message', 10, 2 );

function custom_gf_validation_message( $message, $form ) {
    return '<div class="validation_error">Please correct the errors below and resubmit the form.</div>';
}

Change the message for a specific form

add_filter( 'gform_validation_message_2', 'custom_gf_message_form2', 10, 2 );

function custom_gf_message_form2( $message, $form ) {
    return '<div class="validation_error">Form "' . esc_html( $form['title'] ) . '" could not be submitted. Please check highlighted fields.</div>';
}

Replace 2 in gform_validation_message_2 with your form's ID.

Verify

Submit the target form with a required field left blank. The custom validation message should appear at the top of the form.

Notes

  • The filter receives $message (the default HTML string) and $form (the form array), giving you access to form metadata like the title and ID.
  • Always sanitize any dynamic content inserted into the message string.

Customize the WordPress Login Page

WordPress's default login page uses the WordPress logo and generic styles. You can replace the logo, change colors, and add custom CSS to match your site's branding — all without a plugin.

Prerequisites

  • A child theme or site plugin for custom code
  • A logo image uploaded to your media library or theme directory

Steps

1. Replace the login logo

add_action( 'login_enqueue_scripts', 'custom_login_logo' );

function custom_login_logo() {
    $logo_url = get_stylesheet_directory_uri() . '/images/logo.png';
    echo '<style>
        #login h1 a {
            background-image: url(' . esc_url( $logo_url ) . ');
            background-size: contain;
            width: 200px;
            height: 80px;
        }
    </style>';
}

2. Fix the logo link and title

add_filter( 'login_headerurl', fn() => home_url() );
add_filter( 'login_headertext', fn() => get_bloginfo( 'name' ) );

3. Customize colors and layout

add_action( 'login_enqueue_scripts', 'custom_login_styles' );

function custom_login_styles() {
    echo '<style>
        body.login {
            background-color: #1a1a2e;
        }
        #login {
            padding: 40px 0;
        }
        .login form {
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.2);
        }
        .wp-core-ui .button-primary {
            background: #0f3460;
            border-color: #0f3460;
        }
    </style>';
}

Verify

Log out and visit /wp-login.php. Your custom logo, colors, and link should appear.

Notes

  • All login-page hooks run before the full WordPress theme loads, so only inline styles or scripts enqueued via login_enqueue_scripts take effect.
  • For extensive customization, plugins like LoginPress or Custom Login Page Customizer provide a visual editor.

Style the WPML Language Switcher with CSS

WPML's language switcher outputs a dropdown or list with predictable CSS IDs and classes. You can reposition and restyle it to fit your theme's header or navigation area.

Prerequisites

  • WPML installed with the language switcher enabled
  • Access to your theme's stylesheet or Customizer Additional CSS

Common CSS Selectors

SelectorElement
#lang_selOuter switcher container
#lang_sel a.lang_sel_selCurrently selected language link
#lang_sel liEach language list item
#lang_sel li ulDropdown list

Example Styling

/* Reposition the switcher */
#lang_sel {
    position: relative;
    top: 6px;
    margin-left: -25px;
}

/* Style the selected language button */
#lang_sel a.lang_sel_sel {
    display: inline-block;
    padding: 7px 12px;
    font-size: 12px;
    line-height: 25px;
    border: 1px solid #bbb;
    border-radius: 10px;
    width: 100px;
    text-align: center;
}

/* Hide flags, show text only */
#lang_sel .iclflag {
    display: none;
}

/* Dropdown items */
#lang_sel li ul {
    position: absolute;
    background: #fff;
    border: 1px solid #ddd;
    min-width: 100px;
    z-index: 999;
}

#lang_sel li ul li a {
    display: block;
    padding: 6px 12px;
    color: #333;
    text-decoration: none;
}

#lang_sel li ul li a:hover {
    background: #f5f5f5;
}

Verify

Switch between languages on the front end and confirm the switcher displays and positions correctly.

Notes

  • WPML also supports a widget-based switcher — place the WPML Language Switcher widget in any registered sidebar.
  • If you use WPML's navigation language switcher (integrated into a menu), target .wpml-ls-menu-item instead.

Change WooCommerce Add to Cart Button Text

WooCommerce uses separate filter hooks to control the "Add to Cart" button text on single product pages and product archive/shop pages. Add the relevant filters to your child theme's functions.php or a custom plugin.

Prerequisites

  • WooCommerce installed and active
  • Access to functions.php (child theme) or a custom plugin

Steps

Change text on single product pages

add_filter( 'woocommerce_product_single_add_to_cart_text', 'custom_single_add_to_cart_text' );

function custom_single_add_to_cart_text() {
    return 'Buy Now';
}

Change text on shop/archive pages

add_filter( 'woocommerce_product_add_to_cart_text', 'custom_archive_add_to_cart_text', 10, 1 );

function custom_archive_add_to_cart_text( $product ) {
    return 'Shop Now';
}

Change text by product type (archive)

add_filter( 'woocommerce_product_add_to_cart_text', 'custom_text_by_type', 10, 1 );

function custom_text_by_type( $product ) {
    switch ( $product->get_type() ) {
        case 'external':
            return 'Visit Store';
        case 'grouped':
            return 'View Products';
        case 'simple':
        default:
            return 'Add to Cart';
    }
}

Verify

Visit a product archive page and a single product page. The button text should reflect your custom strings.

Notes

  • These filters change the button label only — they do not affect functionality.
  • If you need per-product custom text, store it in a custom field and retrieve it inside the filter callback using the product object.

Eliminate Render-Blocking JavaScript and CSS in WordPress

Render-blocking resources prevent the browser from displaying page content until they finish loading. Google PageSpeed Insights flags these as a performance issue. This guide shows how to defer JavaScript and inline critical CSS in WordPress.

Prerequisites

  • WordPress admin access
  • A caching or optimization plugin (recommended), or direct access to functions.php

Steps

Option A — Use an optimization plugin (recommended)

Plugins that handle render-blocking resources automatically:

  • WP Rocket — defer JS, optimize CSS delivery
  • Autoptimize — aggregate and defer scripts, inline critical CSS
  • LiteSpeed Cache — defer JS, async CSS loading

Option B — Defer non-critical JavaScript via functions.php

add_filter( 'script_loader_tag', 'defer_non_critical_scripts', 10, 3 );

function defer_non_critical_scripts( $tag, $handle, $src ) {
    $defer = array( 'my-plugin-script', 'slider-js', 'analytics' );
    if ( in_array( $handle, $defer ) ) {
        return str_replace( ' src', ' defer src', $tag );
    }
    return $tag;
}

Option C — Load CSS asynchronously

// Convert blocking stylesheet to preload + noscript fallback
add_filter( 'style_loader_tag', 'async_load_css', 10, 4 );

function async_load_css( $html, $handle, $href, $media ) {
    $async_handles = array( 'non-critical-style' );
    if ( in_array( $handle, $async_handles ) ) {
        $html  = "<link rel='preload' as='style' onload="this.onload=null;this.rel='stylesheet'" href='" . $href . "'>
";
        $html .= "<noscript><link rel='stylesheet' href='" . $href . "'></noscript>
";
    }
    return $html;
}

Verify

Run a PageSpeed Insights test before and after. The "Eliminate render-blocking resources" warning should be reduced or eliminated.

Notes

  • Never defer critical CSS or scripts needed for above-the-fold rendering — doing so will cause a flash of unstyled content (FOUC).
  • Always test on a staging site before applying to production.

Fix Render-Blocking Resources in WordPress Above-the-Fold

Google PageSpeed Insights' "Eliminate render-blocking resources" warning specifically targets scripts and stylesheets that delay above-the-fold rendering. This guide covers practical fixes using both plugins and code.

Prerequisites

  • WordPress admin access
  • A staging environment for testing changes

Steps

1. Identify render-blocking resources

Run your URL through PageSpeed Insights and note each resource flagged under "Eliminate render-blocking resources".

2. Move scripts to the footer

When registering scripts, set the $in_footer parameter to true:

wp_enqueue_script( 'my-script', get_stylesheet_directory_uri() . '/js/main.js', array(), '1.0', true );

3. Add async or defer attributes to third-party scripts

add_filter( 'script_loader_tag', 'add_async_defer', 10, 2 );

function add_async_defer( $tag, $handle ) {
    $async  = array( 'google-analytics', 'facebook-pixel' );
    $defer  = array( 'slider-script', 'accordion-js' );

    if ( in_array( $handle, $async ) ) {
        return str_replace( '></script>', ' async></script>', $tag );
    }
    if ( in_array( $handle, $defer ) ) {
        return str_replace( '></script>', ' defer></script>', $tag );
    }
    return $tag;
}

4. Inline critical CSS and load the rest asynchronously

Extract the CSS needed to render above-the-fold content (using a tool like criticalcss.com) and inline it in <head>. Load the full stylesheet asynchronously as shown in the previous guide.

Verify

Re-run PageSpeed Insights. The render-blocking score should improve. Check the site visually to ensure no layout regressions.

Notes

  • Use async for independent scripts (analytics). Use defer for scripts that depend on the DOM being ready.
  • Do not add async or defer to jQuery or any script that other scripts depend on without also deferring its dependents.

Recommended WordPress Plugins List

This is a curated list of WordPress plugins commonly used for performance, security, development utilities, and site management.

Performance

  • WP Deferred JavaScripts — defers JavaScript loading to improve page speed
  • Above The Fold Optimization — prioritizes critical CSS and defers non-critical resources
  • Autoptimize — aggregates, minifies, and caches CSS and JavaScript
  • WP Super Cache / W3 Total Cache — full-page caching

Analytics and Tracking

  • Insert Headers and Footers — inject Google Analytics, Tag Manager, or any script snippet into <head> or before </body> without editing theme files

Development Utilities

  • CYSTEME Finder — a file explorer for WordPress admin (wordpress.org/plugins/cysteme-finder/)
  • WordPress Reset — resets the database to a fresh WordPress install (use only on development sites)
  • Duplicator — packages and migrates WordPress sites
  • Duplicate Post — clone posts and pages with one click
  • Theme File Duplicator — clone an existing theme template file from the WordPress admin under Appearance → Theme File Duplicator
  • WP-CLI — command-line interface for WordPress management (not a plugin, installed on the server)

SEO

  • Yoast SEO / Rank Math — on-page SEO analysis, XML sitemaps, breadcrumbs

Security

  • Wordfence Security — firewall and malware scanner
  • Sucuri Security — security hardening and monitoring
  • UpdraftPlus — scheduled backups to remote storage

Notes

  • Only install plugins you actively use — each plugin adds to page load time and potential attack surface.
  • Keep all plugins updated to receive security patches.

Add a Sidebar to a WordPress Theme

WordPress themes can have multiple sidebar areas (widget areas). This guide shows how to register a new sidebar and display it in a template — useful when your theme doesn't include a second or custom sidebar by default.

Prerequisites

  • A child theme with functions.php and a template file you can edit

Steps

1. Register the sidebar in functions.php

add_action( 'widgets_init', 'register_custom_sidebars' );

function register_custom_sidebars() {
    register_sidebar( array(
        'name'          => 'Secondary Sidebar',
        'id'            => 'sidebar-secondary',
        'description'   => 'Widgets in this area appear in the secondary sidebar.',
        'before_widget' => '<div id="%1$s" class="widget %2$s">',
        'after_widget'  => '</div>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ) );
}

2. Display the sidebar in a template

Open the template file where you want the sidebar to appear (e.g., sidebar.php, page.php, or a custom template) and add:

<?php if ( is_active_sidebar( 'sidebar-secondary' ) ) : ?>
    <aside class="widget-area secondary-sidebar">
        <?php dynamic_sidebar( 'sidebar-secondary' ); ?>
    </aside>
<?php endif; ?>

3. Add widgets to the new sidebar

Go to Appearance → Widgets and drag widgets into the Secondary Sidebar area.

Verify

Visit the page where the template is used. The widgets added to the Secondary Sidebar should appear.

Notes

  • You can place widget areas in headers, footers, or any part of the layout — not just traditional sidebars.
  • The is_active_sidebar() check prevents rendering empty wrapper markup when no widgets are assigned.

Test if WordPress/PHP Can Send Email

Before troubleshooting WordPress email issues, confirm whether the server's PHP mail function is working at all. This lets you isolate whether the problem is in WordPress, the mail function, or the server's mail configuration.

Prerequisites

  • SSH access to the server, or access to a PHP file in the WordPress root

Steps

1. Test PHP mail from the command line

php -r "mail('you@example.com', 'Test Subject', 'Test body from PHP CLI'); echo 'Sent';"

2. Test via a temporary PHP file

Create mail-test.php in the WordPress root:

<?php
$to      = 'you@example.com';
$subject = 'PHP Mail Test';
$message = 'If you receive this, PHP mail() is working.';
$headers = 'From: test@' . $_SERVER['HTTP_HOST'];

if ( mail( $to, $subject, $message, $headers ) ) {
    echo 'mail() returned true — check your inbox.';
} else {
    echo 'mail() returned false — server cannot send mail.';
}
?>

Visit the file in your browser, then delete it immediately.

3. Test WordPress's wp_mail() function

<?php
require_once 'wp-load.php';
$result = wp_mail( 'you@example.com', 'WP Mail Test', 'Testing wp_mail().' );
echo $result ? 'wp_mail() succeeded.' : 'wp_mail() failed.';
?>

Verify

Check the target inbox. If PHP mail() succeeds but wp_mail() fails, the issue is in WordPress configuration or a plugin hook. If both fail, the problem is server-level.

Notes

  • Many shared hosts block the PHP mail() function to prevent spam. Use SMTP via a plugin like WP Mail SMTP as a reliable alternative.
  • Check server mail logs (/var/log/mail.log) for error messages if emails are not received.

Fix WordPress Not Sending Email

WordPress uses the PHP mail() function by default, which many hosting providers restrict or disable. The most reliable fix is to configure WordPress to send email via an authenticated SMTP server.

Prerequisites

  • SMTP credentials from your email provider (Gmail, SendGrid, Mailgun, Amazon SES, etc.)
  • WordPress admin access

Steps

Option A — Use a plugin (recommended)

  1. Install and activate WP Mail SMTP (or Post SMTP).
  2. Go to WP Mail SMTP → Settings and select your mailer (Gmail, SendGrid, etc.).
  3. Enter your SMTP host, port, username, and password (or API key).
  4. Use the built-in Email Test tool to send a test message.

Option B — Configure SMTP manually in functions.php

add_action( 'phpmailer_init', 'configure_smtp' );

function configure_smtp( $phpmailer ) {
    $phpmailer->isSMTP();
    $phpmailer->Host       = 'smtp.gmail.com';
    $phpmailer->SMTPAuth   = true;
    $phpmailer->Port       = 587;
    $phpmailer->Username   = 'you@gmail.com';
    $phpmailer->Password   = 'your-app-password';
    $phpmailer->SMTPSecure = 'tls';
    $phpmailer->From       = 'you@gmail.com';
    $phpmailer->FromName   = 'Your Site Name';
}

Verify

Send a test email using the plugin's test tool or by triggering a WordPress action (e.g., a contact form submission). Confirm delivery in the recipient's inbox.

Troubleshooting

  • Gmail blocked sign-in: Enable 2FA on your Google account and generate an App Password to use instead of your account password.
  • Port 587 blocked: Try port 465 with SMTPSecure = 'ssl', or ask your host which SMTP ports are open.
  • Emails go to spam: Set up SPF, DKIM, and DMARC DNS records for your sending domain.

Optimize Your WordPress Database

WordPress databases accumulate overhead over time — post revisions, trashed items, expired transients, and orphaned metadata. Regular optimization keeps query performance high and reduces database size.

Prerequisites

  • WordPress admin access
  • A recent database backup before making any changes

Steps

Option A — Plugin (easiest)

  1. Install WP-Optimize or Advanced Database Cleaner.
  2. Run a scan to identify items to clean (revisions, spam comments, transients, orphaned data).
  3. Select the categories to remove and click Clean.
  4. Use the Optimize Tables function to run OPTIMIZE TABLE on all tables.

Option B — WP-CLI

# Delete all post revisions
wp post delete $(wp post list --post_type='revision' --format=ids) --force

# Delete expired transients
wp transient delete --expired

# Optimize all database tables
wp db optimize

Option C — MySQL directly

-- Remove all post revisions
DELETE FROM wp_posts WHERE post_type = 'revision';

-- Remove orphaned postmeta for deleted posts
DELETE pm FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL;

-- Remove expired transients
DELETE FROM wp_options
WHERE option_name LIKE '_transient_%'
  AND option_name NOT LIKE '_transient_timeout_%';

-- Optimize tables
OPTIMIZE TABLE wp_posts, wp_postmeta, wp_options, wp_comments;

Verify

Check database size in phpMyAdmin before and after. Query response times in Query Monitor plugin should also decrease.

Notes

  • Schedule regular optimization via a plugin's built-in scheduler (weekly is usually sufficient for active sites).
  • Limit post revisions to reduce accumulation: add define( 'WP_POST_REVISIONS', 3 ); to wp-config.php.
  • Never run database operations directly on production without a verified backup.

Remove Query Strings from Static Resources in WordPress

WordPress appends version query strings (e.g., ?ver=5.9) to CSS and JavaScript URLs. Proxy caches and CDNs sometimes cannot cache URLs with query strings, which can hurt performance. This guide removes those strings.

Prerequisites

  • Access to functions.php (child theme or plugin)

Steps

Add the filter to functions.php

add_filter( 'script_loader_src', 'remove_query_strings', 15, 1 );
add_filter( 'style_loader_src',  'remove_query_strings', 15, 1 );

function remove_query_strings( $src ) {
    if ( strpos( $src, '?ver=' ) ) {
        $src = remove_query_arg( 'ver', $src );
    }
    return $src;
}

Alternatively, strip the entire query string:

function strip_all_query_strings( $src ) {
    $parts = explode( '?', $src );
    return $parts[0];
}
add_filter( 'script_loader_src', 'strip_all_query_strings', 15, 1 );
add_filter( 'style_loader_src',  'strip_all_query_strings', 15, 1 );

Verify

View page source and check that CSS and JS URLs no longer contain ?ver=. Run a PageSpeed Insights test to confirm the "Serve static assets with an efficient cache policy" score improves.

Notes

  • Removing version strings means your CDN may continue serving old cached files after updates. Use a cache-busting mechanism (e.g., purge CDN cache on deployment) to mitigate this.
  • This filter does not affect admin pages — it only applies to front-end asset loading.

Force Custom Post Type to be Private in WordPress

Sometimes you need a custom post type to always be private — visible only to logged-in users with sufficient permissions — regardless of what an editor sets in the publishing panel. This can be enforced with a filter on post insertion.

Prerequisites

  • Access to functions.php (child theme or plugin)
  • The post type slug you want to force private

Steps

Add the filter to functions.php

add_filter( 'wp_insert_post_data', 'force_post_type_private', 10, 2 );

function force_post_type_private( $data, $postarr ) {
    if ( $data['post_type'] === 'my_post_type' ) {
        $data['post_status'] = 'private';
    }
    return $data;
}

Replace my_post_type with your custom post type's slug.

Verify

Create or update a post of the target type and attempt to publish it. Check in the database or via the admin list that its status is recorded as private.

Notes

  • The wp_insert_post_data filter fires on both new posts and updates, so existing posts set to public will be overridden on the next save.
  • To retroactively set existing posts to private, run a WP-CLI or SQL update:
    UPDATE wp_posts SET post_status = 'private'
    WHERE post_type = 'my_post_type' AND post_status = 'publish';

Convert WordPress Theme Modifications to a Child Theme Plugin

Code added to a theme's functions.php is lost when the theme is updated or changed. Packaging that code as a small plugin makes it theme-independent and reusable across sites.

Prerequisites

  • FTP or file manager access to the wp-content/plugins/ directory

Steps

1. Create the plugin directory and file

Create a folder at wp-content/plugins/my-site-functions/ and inside it create my-site-functions.php:

<?php
/**
 * Plugin Name: My Site Functions
 * Description: Site-specific functionality, independent of the active theme.
 * Version:     1.0.0
 * Author:      Your Name
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

// === Enqueue custom scripts ===
add_action( 'wp_enqueue_scripts', 'msf_enqueue_scripts' );
function msf_enqueue_scripts() {
    wp_enqueue_style( 'msf-style', plugin_dir_url( __FILE__ ) . 'css/custom.css' );
}

// === Custom post type registration ===
add_action( 'init', 'msf_register_post_types' );
function msf_register_post_types() {
    // register_post_type( ... );
}

// === Move all other theme functions here ===

2. Move code from functions.php to the plugin

Cut the custom functions from your theme's functions.php and paste them into the plugin file, then activate the plugin from Plugins → Installed Plugins.

Verify

Deactivate and reactivate the plugin. Confirm all previously working features (menus, shortcodes, custom post types, etc.) still function correctly.

Notes

  • Use plugin_dir_url( __FILE__ ) and plugin_dir_path( __FILE__ ) for paths inside the plugin, not get_template_directory_uri().
  • Prefix all function, class, and constant names with a unique identifier (e.g., msf_) to avoid conflicts.
  • This approach is especially valuable for client sites — functionality survives a theme swap or redesign.

Set Proper WordPress Filesystem Permissions

Incorrect file and directory permissions are a common WordPress security vulnerability. This guide covers the recommended permission model and how to apply it.

Prerequisites

  • SSH access to the server
  • Knowledge of your web server user (typically www-data on Ubuntu/Debian, apache on CentOS)

Recommended Permission Model

ResourcePermissionNotes
Directories755Owner can write; group and others can read/execute
Files644Owner can read/write; group and others can read only
wp-config.php600 or 640Only the owner can read/write
.htaccess644Readable by web server, writable only by owner

Steps

Apply permissions recursively

# Set ownership (replace www-data with your web server user)
sudo chown -R www-data:www-data /var/www/html/wordpress

# Set directory permissions
sudo find /var/www/html/wordpress -type d -exec chmod 755 {} \;

# Set file permissions
sudo find /var/www/html/wordpress -type f -exec chmod 644 {} \;

# Harden wp-config.php
sudo chmod 600 /var/www/html/wordpress/wp-config.php

Allow WordPress to write to wp-content (uploads, plugins, themes)

sudo chmod -R 755 /var/www/html/wordpress/wp-content
sudo chown -R www-data:www-data /var/www/html/wordpress/wp-content

Verify

Check permissions with ls -la. Then log in to WordPress and confirm you can upload media, install plugins, and update themes without FTP credentials being requested.

Notes

  • Never set directories to 777 — this grants write access to any user on the server.
  • If WordPress still asks for FTP credentials after setting correct ownership, add define( 'FS_METHOD', 'direct' ); to wp-config.php.
  • On shared hosting, the web server user and file owner are often the same account, making stricter permissions (e.g., 750) appropriate.

Download Only WordPress Update Files

WordPress offers "no-content" update packages that contain only core files — excluding the wp-content directory. These smaller downloads are useful when you want to update core files without overwriting your themes, plugins, or uploads.

Prerequisites

  • Knowledge of your current WordPress version
  • FTP or SSH access to upload files

Steps

1. Download the no-content package

Replace X.X.X with your target WordPress version:

wget https://downloads.wordpress.org/release/wordpress-X.X.X-no-content.zip

Example for WordPress 6.5.1:

wget https://downloads.wordpress.org/release/wordpress-6.5.1-no-content.zip

2. Extract and upload

unzip wordpress-X.X.X-no-content.zip
rsync -av --exclude='wp-content' wordpress/ /var/www/html/wordpress/

3. Run the database upgrade if prompted

Visit https://example.com/wp-admin/upgrade.php after uploading files to run any required database migrations.

Verify

Log in to the WordPress admin. The dashboard should show the new version number under Dashboard → Updates.

Notes

  • No-content packages are ideal for automated deployment pipelines where wp-content is managed separately (e.g., in version control).
  • Always back up the database before any core update.
  • Full packages (including wp-content) are available at https://downloads.wordpress.org/release/wordpress-X.X.X.zip.

Reset WordPress Password Using MySQL

If you cannot access the WordPress admin to reset a password, you can update it directly in the MySQL database. WordPress stores passwords as MD5 hashes (for direct SQL) or as phpass hashes (via WP-CLI).

Prerequisites

  • MySQL access (command line, phpMyAdmin, or a DB management tool)
  • The username or user ID of the account to reset

Steps

Option A — WP-CLI (most secure)

wp user update admin --user_pass='NewSecurePassword!'

Option B — MySQL command line

# Log in to MySQL
mysql -u root -p

# Select the WordPress database
USE your_database_name;

# Find the user ID
SELECT ID, user_login FROM wp_users;

# Update the password (replace 1 with the correct user ID)
UPDATE wp_users
SET user_pass = MD5('NewSecurePassword!')
WHERE ID = 1;

Option C — phpMyAdmin

  1. Open phpMyAdmin and select your WordPress database.
  2. Click the wp_users table, then find and click Edit on the target user row.
  3. In the user_pass field, select MD5 from the function dropdown and enter the new password.
  4. Click Go to save.

Verify

Attempt to log in at /wp-login.php with the new password.

Notes

  • WP-CLI generates a phpass hash, which is more secure than raw MD5. Use it when possible.
  • After a direct SQL reset, WordPress may prompt for a password change on next login, depending on settings.
  • Replace wp_ with your actual table prefix if it differs.

Fix Google Maps "sensor" Parameter Issue in WordPress

The Google Maps JavaScript API removed support for the sensor parameter in version 3.22 (2016). Themes and plugins that still pass ?sensor=false or ?sensor=true may trigger console warnings or loading errors. The fix is to remove the sensor parameter and add a valid API key.

Prerequisites

  • A Google Maps API key (obtain one from the Google Cloud Console)
  • Access to the plugin or theme file that enqueues the Maps script

Steps

1. Locate the enqueue call

Search your theme or plugin files for the Maps API URL:

grep -r "maps.google.com/maps/api/js" /var/www/html/wordpress/wp-content/

2. Update the script URL

Change a call like this:

wp_enqueue_script(
    'google-maps',
    'http://maps.google.com/maps/api/js?sensor=false',
    array(), null, true
);

To this:

wp_enqueue_script(
    'google-maps',
    'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY',
    array(), null, true
);

Verify

Open the browser developer console on the page that loads Google Maps. No warnings about the sensor parameter or missing API key should appear, and the map should load correctly.

Notes

  • If you cannot edit the plugin directly (risk of being overwritten on update), use a filter to modify the script URL:
    add_filter( 'script_loader_src', function( $src ) {
        if ( strpos( $src, 'maps.google.com' ) !== false ) {
            $src = remove_query_arg( 'sensor', $src );
            $src = add_query_arg( 'key', 'YOUR_API_KEY', $src );
        }
        return $src;
    } );
  • Restrict your API key to specific referrer domains in the Google Cloud Console to prevent unauthorized usage.

Common WordPress Error Fixes

This guide covers several common PHP and WordPress errors and their fixes.

Error: is_array() used on array_key_exists() result

The function array_key_exists() returns a boolean, not an array. Wrapping it in is_array() is incorrect and always returns false.

Incorrect:

define( 'CONSUMER_KEY', ( is_array( array_key_exists( 'twitter_consumerkey', $optionTwitter ) ) )
    ? $optionTwitter['twitter_consumerkey'] : '' );

Correct:

define( 'CONSUMER_KEY', ( array_key_exists( 'twitter_consumerkey', $optionTwitter ) )
    ? $optionTwitter['twitter_consumerkey'] : '' );

Error: White Screen of Death (WSOD)

Enable debug mode in wp-config.php to reveal the underlying PHP error:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Check wp-content/debug.log for the error message. Common causes: a missing semicolon, undefined function, or a plugin conflict.

Error: "Sorry, you are not allowed to access this page"

This usually indicates corrupted user capabilities. Fix via WP-CLI:

wp user add-role 1 administrator

Or via SQL:

UPDATE wp_usermeta SET meta_value = 'a:1:{s:13:"administrator";b:1;}'
WHERE user_id = 1 AND meta_key = 'wp_capabilities';

Error: "The site is experiencing technical difficulties"

Added in WordPress 5.2, this message replaces the white screen. A recovery email is sent to the admin address. Check wp-content/debug.log or temporarily enable WP_DEBUG_DISPLAY to view the error on screen.

Notes

  • Always disable WP_DEBUG_DISPLAY on production sites to prevent error details from being visible to visitors.
  • Deactivating plugins via FTP (rename the plugin folder) is the quickest way to isolate a plugin-caused error without admin access.

Reset a WordPress User Password via MySQL

This guide shows how to reset a WordPress user's password directly through the MySQL command line — useful when the admin panel is inaccessible.

Prerequisites

  • SSH access with MySQL client installed, or access to phpMyAdmin
  • The WordPress database name and table prefix

Steps

# Log in to MySQL
mysql -u root -p

# Select the WordPress database
USE your_wordpress_db;

# Find the user whose password you want to reset
SELECT ID, user_login, user_pass FROM wp_users;

# Reset the password (replace the ID and password)
UPDATE wp_users
SET user_pass = MD5('NewStrongPassword!')
WHERE ID = 1;

# Confirm the change
SELECT ID, user_login, user_pass FROM wp_users WHERE ID = 1;

Verify

Exit MySQL and attempt to log in to WordPress at /wp-login.php with the new password.

Troubleshooting

  • Login still fails after MD5 reset: WordPress uses phpass hashing internally. A direct MD5 hash in the DB is valid for initial login but WordPress will rehash it automatically after a successful login. If it still fails, try resetting via WP-CLI: wp user update 1 --user_pass='NewPassword!'
  • Table prefix differs: Replace wp_users with yourprefix_users as defined in wp-config.php.

Notes

  • After resetting via MySQL, immediately log in and change the password through the WordPress profile page to upgrade the hash to phpass.
  • Consider also resetting the user's auth cookies by updating the session_tokens usermeta or using wp_destroy_all_sessions() via WP-CLI.

Fix WordPress Permission Errors on Bitnami

Bitnami WordPress stacks use a non-standard directory structure and run the web server under a specific user. This can cause WordPress to request FTP credentials when installing plugins, updating themes, or uploading files. The fix bypasses FTP by telling WordPress to write directly.

Prerequisites

  • SSH access to the Bitnami server
  • sudo privileges

Steps

1. Set FS_METHOD to direct in wp-config.php

sudo sh -c "echo "define('FS_METHOD','direct');" >> /opt/bitnami/apps/wordpress/htdocs/wp-config.php"

This tells WordPress to use direct file system access instead of FTP.

2. Fix file ownership

# Bitnami uses 'daemon' as the web server user
sudo chown -R daemon:daemon /opt/bitnami/apps/wordpress/htdocs/wp-content

3. Set correct permissions

sudo find /opt/bitnami/apps/wordpress/htdocs -type d -exec chmod 755 {} \;
sudo find /opt/bitnami/apps/wordpress/htdocs -type f -exec chmod 644 {} \;

Bitnami Stack Reference

  • Stack root: /opt/bitnami/
  • WordPress files: /opt/bitnami/apps/wordpress/htdocs/
  • Service manager: /opt/bitnami/ctlscript.sh start|stop|restart apache

Verify

Log in to the WordPress admin and attempt to install a plugin. WordPress should install it directly without requesting FTP credentials.

Notes

  • The Bitnami web server user may be daemon or www-data depending on the stack version — verify with ps aux | grep apache.
  • For newer Bitnami stacks using Lightsail or Docker, the configuration paths may differ — refer to the Bitnami documentation for your specific stack.

WordPress.com DNS Records Reference

When using a custom domain with WordPress.com, you need to add specific DNS records at your domain registrar or DNS provider. This guide covers the required record types and WordPress.com's TXT record length limitation.

Prerequisites

  • Access to your domain's DNS management panel
  • A WordPress.com site with a custom domain mapped

Required DNS Records

TypeNameValue
A@192.0.78.212
A@192.0.78.213
CNAMEwwwyoursite.wordpress.com

TXT Record Length Limitation

WordPress.com's DNS editor supports up to 255 characters per TXT record. For longer records (e.g., DKIM keys), split the value across two records using a forward slash as a continuation marker:

# TXT Record 1
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."

# If it exceeds 255 chars, split:
# TXT Record 1
"v=DKIM1; k=rsa; p=firstpartofthekeyhere/"

# TXT Record 2
"remainingpartofthekeyhere"

Verify

Use a DNS propagation checker (e.g., dnschecker.org) to confirm records have propagated globally. DNS changes can take up to 48 hours.

Notes

  • If your registrar is also your DNS provider, you may be able to use WordPress.com's nameservers directly for simpler management.
  • For email delivery, also add MX and SPF records provided by your email provider.

Fix CORS Issues After Moving a Domain with Avada Theme

After moving a WordPress site to a new domain, Avada Theme may serve CSS, JavaScript, or font files from the old domain URL — triggering CORS (Cross-Origin Resource Sharing) errors in the browser. This happens because Avada caches dynamic CSS and JS with the old domain embedded.

Prerequisites

  • WordPress admin access
  • FTP access to verify cache folders are cleared

Steps

1. Clear Avada's dynamic CSS and JS cache

  1. Go to Avada → Theme Options → Advanced.
  2. Find the Dynamic CSS & JS section and click Reset Avada Caches.
  3. Verify via FTP that the cache folders under wp-content/uploads/avada-styles/ have been emptied.

2. Resave pages that used custom fonts or Fusion Builder elements

  1. Open each affected page in Fusion Builder (Avada's page builder).
  2. Make a minor change (or none) and click Update.
  3. This forces Avada to regenerate the CSS/JS with the correct new domain.

3. Update the WordPress site URL

Confirm the site URL is correctly updated in Settings → General. If it still shows the old domain, update it in wp-config.php:

define( 'WP_HOME',    'https://newdomain.com' );
define( 'WP_SITEURL', 'https://newdomain.com' );

4. Run a search-replace for the old domain

wp search-replace 'https://olddomain.com' 'https://newdomain.com' --skip-columns=guid

Verify

Open the browser developer tools (Network tab) and reload the page. All resources should load from the new domain with no CORS errors in the console.

Notes

  • Also clear any server-level caching (Varnish, Nginx FastCGI cache) and any CDN cache after the domain change.
  • If using a caching plugin (WP Rocket, W3 Total Cache), purge its cache as well.

Remove the 512MB Size Limit on All-in-One WP Migration Plugin

The free version of All-in-One WP Migration limits imports to 512MB. You can raise or remove this limit by modifying a plugin constant — without purchasing the paid extension.

Prerequisites

  • All-in-One WP Migration plugin installed
  • FTP or file manager access to the plugin directory

Steps

Edit the plugin's constants file

Open the file:

/wp-content/plugins/all-in-one-wp-migration/constants.php

Find the line defining the upload size limit:

define( 'AI1WM_MAX_FILE_SIZE', 536870912 ); // 512MB in bytes

Replace it with a higher value or remove the restriction entirely:

// Set to 2GB
define( 'AI1WM_MAX_FILE_SIZE', 2 * 1024 * 1024 * 1024 );

// Or remove the limit entirely (use with caution)
define( 'AI1WM_MAX_FILE_SIZE', PHP_INT_MAX );

Also increase PHP upload limits

In php.ini (or .htaccess / wp-config.php):

upload_max_filesize = 2G
post_max_size = 2G
memory_limit = 512M
max_execution_time = 600

Verify

Go to All-in-One WP Migration → Import. The maximum upload size displayed should reflect your new limit.

Notes

  • This modification will be overwritten when the plugin is updated — re-apply after each plugin update.
  • For frequent large migrations, consider using a server-level tool like Duplicator Pro or migrating via SSH/rsync + database import to avoid upload size restrictions altogether.

Customize the WooCommerce My Account Section

The WooCommerce "My Account" page is fully hookable. You can add, remove, and reorder navigation tabs, as well as customize the content of each section — all without modifying WooCommerce core files.

Prerequisites

  • WooCommerce installed and active
  • A child theme or custom plugin for code additions

Steps

1. Remove default menu items

add_filter( 'woocommerce_account_menu_items', 'custom_my_account_menu' );

function custom_my_account_menu( $items ) {
    unset( $items['downloads'] );     // Remove Downloads tab
    unset( $items['payment-methods'] ); // Remove Payment Methods tab
    return $items;
}

2. Add a custom menu item

add_filter( 'woocommerce_account_menu_items', 'add_custom_account_tab' );

function add_custom_account_tab( $items ) {
    $items['my-rewards'] = 'My Rewards';
    return $items;
}

3. Register the endpoint for the custom tab

add_action( 'init', 'register_my_rewards_endpoint' );

function register_my_rewards_endpoint() {
    add_rewrite_endpoint( 'my-rewards', EP_ROOT | EP_PAGES );
}

// Flush rewrite rules on activation (only once)
add_action( 'init', function() {
    flush_rewrite_rules();
} );

4. Output content for the custom tab

add_action( 'woocommerce_account_my-rewards_endpoint', 'my_rewards_content' );

function my_rewards_content() {
    echo '<h3>Your Rewards</h3>';
    echo '<p>You have <strong>0</strong> reward points.</p>';
}

Verify

Log in as a customer and visit the My Account page. The updated tabs and any new tab content should appear correctly.

Notes

  • After adding a new endpoint, go to Settings → Permalinks and click Save to flush rewrite rules — do this once, not on every page load.
  • Reorder tabs by adjusting the array key order in the woocommerce_account_menu_items filter.

Disable WordPress Emoji Script (wp-emoji-release.min.js)

WordPress automatically loads wp-emoji-release.min.js and associated inline styles on every front-end page to support emoji rendering. For professional or performance-sensitive sites that do not use emojis, this script can be safely disabled.

Prerequisites

  • Access to functions.php (child theme or plugin)

Steps

Add the following to your child theme's functions.php or a site plugin:

add_action( 'init', 'disable_wp_emoji' );

function disable_wp_emoji() {
    // Remove emoji from front end
    remove_action( 'wp_head',             'print_emoji_detection_script', 7 );
    remove_action( 'wp_print_styles',     'print_emoji_styles' );
    remove_filter( 'the_content_feed',    'wp_staticize_emoji' );
    remove_filter( 'comment_text_rss',    'wp_staticize_emoji' );
    remove_filter( 'wp_mail',             'wp_staticize_emoji_for_email' );

    // Remove from admin area as well
    remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
    remove_action( 'admin_print_styles',  'print_emoji_styles' );

    // Remove the TinyMCE plugin
    add_filter( 'tiny_mce_plugins', function( $plugins ) {
        return array_diff( $plugins, array( 'wpemoji' ) );
    } );

    // Remove DNS prefetch
    add_filter( 'emoji_svg_url', '__return_false' );
}

Verify

View the page source — wp-emoji-release.min.js and the associated inline emoji detection script should no longer appear in <head>. Run PageSpeed Insights to confirm the removed request.

Notes

  • Modern browsers render most emoji natively — disabling the WordPress emoji script rarely causes visual regressions.
  • If you use a caching plugin, purge the cache after adding this code so the changes take effect immediately.

Set Up a WordPress Cluster for High-Traffic Sites

A WordPress cluster distributes traffic and database load across multiple servers, enabling horizontal scaling for high-traffic sites. This guide outlines the core architecture and configuration steps.

Prerequisites

  • Multiple servers (or cloud instances) — typically a load balancer, 2+ web servers, and a database server
  • A shared file system for wp-content/uploads (e.g., NFS, Amazon EFS, or object storage with offload plugin)
  • Command-line access to all servers

Architecture Overview

  • Load Balancer — distributes HTTP requests (HAProxy, Nginx, or a cloud load balancer)
  • Web Servers (×2+) — each runs PHP-FPM + Nginx/Apache with a copy of WordPress core
  • Shared File Storagewp-content/uploads mounted via NFS or replaced with an S3-compatible offload plugin
  • Database Server — MySQL/MariaDB, optionally with a read replica
  • Object Cache — Redis or Memcached for WordPress object caching (shared across all web nodes)

Steps

1. Set up shared uploads storage

# On the NFS server
sudo apt install nfs-kernel-server
sudo mkdir -p /exports/wp-uploads
echo "/exports/wp-uploads  web-server-IP(rw,sync,no_subtree_check)" | sudo tee -a /etc/exports
sudo exportfs -a

# On each web server
sudo mount nfs-server-IP:/exports/wp-uploads /var/www/html/wp-content/uploads

2. Configure a shared object cache

Install Redis on a dedicated server, then install the Redis Object Cache plugin on WordPress. Add to wp-config.php:

define( 'WP_REDIS_HOST', 'redis-server-IP' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_CACHE', true );

3. Point all web servers to the same database

In each server's wp-config.php:

define( 'DB_HOST', 'db-server-IP' );

4. Configure the load balancer

# Nginx upstream example
upstream wordpress {
    server web1-IP:80;
    server web2-IP:80;
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://wordpress;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Verify

Use a load testing tool (e.g., ab, wrk, or Loader.io) to send concurrent requests. All requests should be served correctly regardless of which web node handles them.

Notes

  • WordPress core files can be deployed to each web node individually or via a shared code deployment tool (Capistrano, Deployer, Ansible).
  • Database write operations still go to a single primary node — for extreme write loads, consider MySQL Group Replication or Galera Cluster.

Set Up a Scalable, Highly Available WordPress Environment

This guide covers running WordPress in a containerized, highly available environment using Docker and an orchestration tool. The stack includes redundant web containers, a shared database, a shared cache layer, and shared file storage.

Prerequisites

  • Docker and Docker Compose (or a Kubernetes cluster)
  • A shared database (RDS, CloudSQL, or a managed MySQL service)
  • A shared Redis instance for object caching
  • Shared storage for wp-content (EFS, NFS, or S3 with offload plugin)

Docker Compose Example

version: '3.8'

services:
  wordpress:
    image: wordpress:php8.2-fpm
    deploy:
      replicas: 3
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: wp_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_uploads:/var/www/html/wp-content/uploads

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: wp_password
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  wp_uploads:
  db_data:

wp-config.php additions for HA

define( 'WP_REDIS_HOST', 'redis' );
define( 'WP_CACHE', true );

// Handle multiple web nodes behind a load balancer
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

Verify

Scale the WordPress service: docker compose up --scale wordpress=3 -d. Run concurrent requests with wrk or ab and verify all responses succeed and WordPress functions correctly.

Notes

  • For Kubernetes deployments, use a Deployment with multiple replicas and a PersistentVolumeClaim using a ReadWriteMany storage class for the uploads volume.
  • Stateful data (database, uploads) must always be stored outside the container — never in the container layer.

Configure WordPress Varnish 4 Cache with Apache or Nginx

Varnish Cache sits in front of your web server and serves cached responses for anonymous visitors, dramatically reducing server load. This guide covers configuring Varnish 4 with WordPress on Apache or Nginx.

Prerequisites

  • Ubuntu/Debian server with root access
  • Apache or Nginx running WordPress on port 8080 (Varnish will listen on port 80)
  • Varnish 4 installed: sudo apt install varnish

Steps

1. Move the web server to port 8080

In Apache: edit /etc/apache2/ports.conf — change Listen 80 to Listen 8080.
In Nginx: change listen 80 to listen 8080 in your server block.

2. Configure Varnish to listen on port 80

Edit /etc/default/varnish:

DAEMON_OPTS="-a :80              -T localhost:6082              -f /etc/varnish/default.vcl              -S /etc/varnish/secret              -s malloc,256m"

3. Create the VCL configuration

Edit /etc/varnish/default.vcl:

vcl 4.0;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
    # Bypass cache for logged-in users and admin
    if (req.http.cookie ~ "wordpress_logged_in" ||
        req.url ~ "^/wp-admin" ||
        req.url ~ "^/wp-login.php") {
        return (pass);
    }

    # Strip cookies for static assets
    if (req.url ~ "\.(css|js|png|jpg|gif|ico|woff2?)$") {
        unset req.http.cookie;
    }
}

sub vcl_backend_response {
    # Cache static assets for 1 day
    if (bereq.url ~ "\.(css|js|png|jpg|gif|ico|woff2?)$") {
        set beresp.ttl = 1d;
    }
    # Cache WordPress pages for 4 hours
    if (beresp.status == 200) {
        set beresp.ttl = 4h;
    }
}

sub vcl_deliver {
    # Add cache hit/miss header for debugging
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

4. Install the Varnish HTTP Cache plugin in WordPress

The plugin sends a BAN request to Varnish when content is updated, purging stale cache entries automatically.

Verify

Check the X-Cache response header with curl -I https://example.com. First request shows MISS, second shows HIT.

Notes

  • Varnish does not support SSL/TLS — place Nginx (as an SSL terminator) or a load balancer in front of Varnish for HTTPS sites.
  • WooCommerce cart and checkout pages must always bypass the cache to avoid serving wrong cart content to different users.

Fix WordPress Admin Menu Showing as Blank

A blank WordPress admin menu is almost always caused by a missing PHP extension. The admin menu relies on PHP XML or other extensions to render icons and menu items correctly.

Prerequisites

  • SSH access to the server
  • Root or sudo privileges

Steps

1. Enable WordPress debug mode to see the actual error

Add to wp-config.php:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Check wp-content/debug.log for PHP errors related to missing extensions.

2. Install the missing PHP extension

The most common culprit is php-xml:

# Ubuntu/Debian
sudo apt install php-xml php-mbstring
sudo systemctl restart php8.2-fpm   # adjust version as needed
sudo systemctl restart apache2       # or nginx

On CentOS/RHEL:

sudo yum install php-xml php-mbstring
sudo systemctl restart php-fpm
sudo systemctl restart httpd

3. Verify loaded PHP extensions

php -m | grep -E "xml|mbstring|json|curl"

Verify

Reload the WordPress admin. The left-hand menu should now display all items with their correct icons.

Notes

  • Other extensions that can cause admin rendering issues: php-gd (images), php-curl (HTTP requests), php-zip (plugin/theme installation).
  • After installing PHP extensions, always restart both PHP-FPM and your web server process.

Optimize Images in WordPress

Images are typically the largest assets on a WordPress page. Optimizing them — through compression, correct sizing, modern formats, and lazy loading — is one of the highest-impact performance improvements you can make.

Prerequisites

  • WordPress admin access
  • A staging environment for testing

Steps

1. Compress images on upload (plugin)

Install ShortPixel, Imagify, or Smush. These plugins automatically compress images when they are uploaded to the media library and can bulk-process existing images.

2. Serve images in WebP format

Plugins like ShortPixel and Imagify can generate WebP versions and serve them to supporting browsers automatically via .htaccess or Nginx rules.

For manual Nginx WebP serving:

location ~* \.(png|jpg|jpeg)$ {
    add_header Vary Accept;
    try_files $uri.webp $uri =404;
}

3. Enable native lazy loading

WordPress adds loading="lazy" to images automatically since WordPress 5.5. For older themes, add it via a filter:

add_filter( 'wp_lazy_loading_enabled', '__return_true' );

4. Set explicit image dimensions

Always specify width and height attributes to prevent layout shifts. WordPress's wp_get_attachment_image() does this automatically.

5. Use the correct image size

Define custom image sizes in functions.php and use them in templates:

add_image_size( 'blog-thumbnail', 600, 400, true );

// In template:
the_post_thumbnail( 'blog-thumbnail' );

6. Deliver images via CDN

Connect a CDN (Cloudflare, BunnyCDN, or Amazon CloudFront) to serve images from edge locations closer to visitors.

Verify

Run PageSpeed Insights or GTmetrix. Image-related recommendations (Properly size images, Serve images in next-gen formats, Defer offscreen images) should be resolved or significantly improved.

Notes

  • Bulk optimization of the existing media library can take significant server time — run it during low-traffic hours.
  • Do not compress images that need to remain lossless (e.g., logos, diagrams with text) — use lossless compression or PNG format for these.

Fix Time to First Byte (TTFB) in WordPress

Time to First Byte (TTFB) measures how long it takes the server to begin sending a response. A high TTFB (above 600ms) usually indicates slow PHP execution, database bottlenecks, or missing caching. This guide covers the primary fixes for WordPress on Nginx.

Prerequisites

  • SSH access to the server
  • WordPress admin access

Steps

1. Enable a full-page caching plugin

Install WP Super Cache, W3 Total Cache, or WP Rocket. Full-page caching serves pre-generated HTML to anonymous visitors, bypassing PHP and database queries entirely — the single biggest TTFB improvement for most sites.

2. Enable FastCGI cache in Nginx

# nginx.conf — add in http block
fastcgi_cache_path /tmp/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

# Server block
location ~ \.php$ {
    fastcgi_cache WORDPRESS;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}

3. Enable OPcache for PHP

In php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2

4. Add a WordPress object cache (Redis)

# Install Redis
sudo apt install redis-server php-redis

# In wp-config.php
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_CACHE', true );

Install the Redis Object Cache plugin and enable it.

5. Optimize the database

Run wp db optimize or use the WP-Optimize plugin to reduce table overhead. Add indexes to the wp_options autoload column if it has grown large.

Verify

Measure TTFB before and after with curl -o /dev/null -s -w "%{time_starttransfer} " https://example.com. Target under 200ms for cached responses.

Notes

  • High TTFB on the very first request after cache expiry (cache miss) is normal — optimize for cache-hit TTFB.
  • Geographic distance between the server and the user also affects TTFB — a CDN with edge caching is the solution for global audiences.

Fix "Elementor Preview Could Not Be Loaded"

The "Elementor Preview Could Not Be Loaded" error appears when the Elementor editor cannot render the page preview. It is commonly caused by an outdated plugin version, PHP version incompatibility, corrupted CSS/template data, or a JavaScript conflict.

Prerequisites

  • WordPress and Elementor admin access

Steps

  1. Update Elementor. Go to Plugins → Installed Plugins and update Elementor (and Elementor Pro if installed) to the latest version.
  2. Check PHP version. Elementor requires PHP 7.4 or higher. In Tools → Site Health → Info → Server, verify the PHP version. If it's below the requirement, upgrade via your hosting control panel.
  3. Regenerate CSS files. Go to Elementor → Tools → Regenerate CSS & Data and click the button. This rebuilds Elementor's dynamic stylesheet.
  4. Sync templates library. Go to Elementor → Tools → General and click Sync Library.
  5. Increase PHP memory limit. Add to wp-config.php:
    define( 'WP_MEMORY_LIMIT', '256M' );
    define( 'WP_MAX_MEMORY_LIMIT', '512M' );
  6. Test for plugin conflicts. Temporarily deactivate all plugins except Elementor and recheck. Reactivate them one by one to identify the conflicting plugin.
  7. Clear all caches. Clear your caching plugin cache, Cloudflare cache, and browser cache, then reload the editor.

Verify

Open the Elementor editor on the affected page. The preview pane should load and display the page content correctly.

Notes

  • Check the browser console for JavaScript errors — they often point directly to the conflicting script.
  • Ensure the WordPress memory limit in php.ini (memory_limit) is also set to 256M or higher, as the wp-config.php value cannot exceed the server's PHP limit.

Fix "Uncaught TypeError: $template.get is not a function" in Visual Composer

The error Uncaught TypeError: $template.get is not a function in Visual Composer (WPBakery Page Builder) is caused by a jQuery version conflict or a corrupted function in the composer-view.js file. The fix involves patching the affected JavaScript function.

Prerequisites

  • FTP or file manager access to the server
  • A backup of the file before editing

Steps

1. Locate the file

/wp-content/plugins/js_composer/assets/js/dist/composer-view.js

2. Find the html2element function

Search for the html2element function in the file. The problematic call looks like:

return $template.get(0);

3. Replace with a jQuery-compatible equivalent

// Before (broken):
return $template.get(0);

// After (fixed):
return jQuery( $template ).get(0);

4. Clear browser and server caches

After saving the file, clear all caches (plugin, CDN, browser) and reload the editor.

Alternative: Resolve the jQuery conflict

The root cause is often another plugin loading a conflicting jQuery version. Check the browser console for multiple jQuery instances and deactivate plugins one by one to find the culprit.

Verify

Open a page in the Visual Composer editor. The editor should load without the $template.get is not a function error in the browser console.

Notes

  • Manual edits to plugin files are overwritten on plugin updates. After resolving the conflict, update Visual Composer to the latest version to get the official fix.
  • This error can also occur when WordPress's jQuery version is lower than what Visual Composer expects — ensure WordPress core and all plugins are up to date.

Fix MySQL "Incorrect datetime value: 0000-00-00 00:00:00" in WordPress

MySQL 5.7 and later enable strict mode by default, which rejects the invalid datetime value 0000-00-00 00:00:00 that older WordPress databases may contain. This causes import errors and query failures. The fix involves adjusting MySQL's sql_mode.

Prerequisites

  • Root or admin access to MySQL
  • SSH access to edit my.cnf

Steps

Option A — Remove strict mode in my.cnf (persistent fix)

Open /etc/mysql/my.cnf (or /etc/mysql/mysql.conf.d/mysqld.cnf) and add or edit:

[mysqld]
sql_mode = "NO_ENGINE_SUBSTITUTION,ERROR_FOR_DIVISION_BY_ZERO"

Restart MySQL:

sudo systemctl restart mysql

Option B — Set sql_mode per session (temporary)

SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION,ERROR_FOR_DIVISION_BY_ZERO';
-- then run your import or query

Option C — Fix the data at the source

Update the affected rows to use a valid datetime before importing:

-- Replace 0000-00-00 dates with NULL or a valid date
UPDATE wp_posts
SET post_date = NULL
WHERE post_date = '0000-00-00 00:00:00';

UPDATE wp_posts
SET post_date_gmt = NULL
WHERE post_date_gmt = '0000-00-00 00:00:00';

Verify

Re-run the import or query. The #1292 error should no longer appear.

Notes

  • The default MySQL 5.7 sql_mode includes NO_ZERO_IN_DATE and NO_ZERO_DATE, which reject zero dates. Removing just those two flags while keeping other strict settings is a more targeted approach than disabling all strict mode.
  • Option C (fixing the data) is the cleanest long-term solution as it makes the database compatible with any MySQL version.

Add a WordPress Admin User via PHP or Database

When you need to create a WordPress administrator but cannot access the admin panel, you can add the user directly via a PHP script or via SQL. This is common when taking over an existing site without login credentials.

Prerequisites

  • FTP or file manager access to the WordPress root, or MySQL access

Option A — PHP Script (recommended)

Create add-admin.php in the WordPress root directory:

<?php
define( 'ABSPATH', dirname( __FILE__ ) . '/' );
require_once( ABSPATH . 'wp-load.php' );

$username  = 'newadmin';
$password  = 'StrongPassword123!';
$email     = 'admin@example.com';

if ( ! username_exists( $username ) && ! email_exists( $email ) ) {
    $user_id = wp_create_user( $username, $password, $email );
    $user    = new WP_User( $user_id );
    $user->set_role( 'administrator' );
    echo 'User created successfully. ID: ' . $user_id;
} else {
    echo 'User already exists.';
}
?>

Visit https://example.com/add-admin.php in your browser, then delete the file immediately.

Option B — Direct MySQL

-- Insert user
INSERT INTO `wp_users`
  (`user_login`, `user_pass`, `user_nicename`,
   `user_email`, `user_registered`, `user_status`, `display_name`)
VALUES
  ('newadmin', MD5('StrongPassword123!'), 'newadmin',
   'admin@example.com', NOW(), 0, 'New Admin');

-- Grant administrator role
INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`)
VALUES
  (LAST_INSERT_ID(), 'wp_capabilities', 'a:1:{s:13:"administrator";b:1;}'),
  (LAST_INSERT_ID(), 'wp_user_level', '10');

Verify

Log in at /wp-login.php with the new credentials. Confirm the user has Administrator role under Users → All Users.

Notes

  • Always delete temporary PHP scripts after use — they expose direct WordPress access to anyone who knows the filename.
  • MD5 is acceptable for the initial SQL insert; WordPress will upgrade the hash to phpass on the first successful login.

Deploy WordPress on Kubernetes

Kubernetes enables horizontal scaling, automated failover, and rolling updates for WordPress. This guide covers deploying WordPress with MySQL on a Kubernetes cluster using Deployments, Services, and PersistentVolumeClaims.

Prerequisites

  • A running Kubernetes cluster (EKS, GKE, AKS, or local with minikube)
  • kubectl configured and connected to the cluster

Steps

1. Create a Secret for database credentials

kubectl create secret generic mysql-secret   --from-literal=mysql-root-password=rootpassword   --from-literal=mysql-password=wppassword

2. Deploy MySQL

# mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: mysql-root-password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wp_user
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: mysql-password
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql
  ports:
  - port: 3306

3. Deploy WordPress

# wordpress-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 2
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - name: wordpress
        image: wordpress:6.5-php8.2-apache
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql
        - name: WORDPRESS_DB_USER
          value: wp_user
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: mysql-password
        - name: WORDPRESS_DB_NAME
          value: wordpress
        ports:
        - containerPort: 80
        volumeMounts:
        - name: wp-uploads
          mountPath: /var/www/html/wp-content/uploads
      volumes:
      - name: wp-uploads
        persistentVolumeClaim:
          claimName: wp-uploads-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-uploads-pvc
spec:
  accessModes: [ "ReadWriteMany" ]  # requires an NFS or EFS storage class
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
spec:
  type: LoadBalancer
  selector:
    app: wordpress
  ports:
  - port: 80
    targetPort: 80

4. Apply the manifests

kubectl apply -f mysql-deployment.yaml
kubectl apply -f wordpress-deployment.yaml

Verify

kubectl get pods
kubectl get svc wordpress   # note the EXTERNAL-IP

Visit the external IP in a browser. WordPress installation screen should appear.

Notes

  • The wp-content/uploads PVC must use a ReadWriteMany access mode so all WordPress pod replicas can write to the same storage. Use an NFS provisioner or Amazon EFS CSI driver.
  • For production, use a managed database service (RDS, Cloud SQL) instead of a MySQL pod to avoid data loss risk.

Reset WordPress Password (All Methods)

WordPress provides several ways to reset a user password, ranging from the built-in email link to direct database updates. This guide covers all methods in order of ease.

Method 1 — WordPress Login Screen

  1. Go to /wp-login.php and click Lost your password?
  2. Enter your username or email address and click Get New Password.
  3. Check your inbox and follow the reset link.

Method 2 — WordPress Admin (for admins resetting other users)

  1. Go to Users → All Users and click the user's name.
  2. Scroll to Account Management and click Generate Password.
  3. Copy or modify the password and click Update User.

Method 3 — WP-CLI

wp user update admin --user_pass='NewSecurePassword!'

Method 4 — MySQL Command Line

mysql -u root -p
USE wordpress_database;
UPDATE wp_users
SET user_pass = MD5('NewSecurePassword!')
WHERE user_login = 'admin';

Method 5 — phpMyAdmin

  1. Open phpMyAdmin and select your WordPress database.
  2. Browse the wp_users table and click Edit on the target user row.
  3. In the user_pass field, select MD5 from the function dropdown and enter the new password.
  4. Click Go.

Verify

Attempt to log in with the new password at /wp-login.php.

Notes

  • If the password reset email is not received, check your spam folder and verify that WordPress can send email (SMTP configuration).
  • After a direct database reset, WordPress will upgrade the MD5 hash to the more secure phpass format on the next successful login.

Prevent WordPress Malware from Infecting the Server

WordPress sites are frequent malware targets. Infections often manifest as redirects on /wp-admin, modified .htaccess files, injected PHP in core files, or outbound spam. This guide covers detection, cleanup, and prevention.

Signs of Infection

  • Unexpected 302 redirects when visiting /wp-admin
  • Modified .htaccess with unknown redirect rules
  • New PHP files in wp-content/uploads/
  • Google Search Console flagging the site for malware

Steps — Cleanup

1. Take the site offline and back up

# Prevent further damage
echo "Maintenance" | sudo tee /var/www/html/index.html
sudo mv /var/www/html/wp-login.php /var/www/html/wp-login.php.bak

2. Scan for malware

# Find recently modified files (last 7 days)
find /var/www/html -name "*.php" -mtime -7 -ls

# Search for common malware patterns
grep -r "eval(base64_decode" /var/www/html --include="*.php"
grep -r "system(" /var/www/html/wp-content/uploads --include="*.php"

3. Remove infected files and replace core files

# Replace WordPress core with a fresh download
wget https://wordpress.org/latest.zip
unzip latest.zip
rsync -av --exclude='wp-content' wordpress/ /var/www/html/

4. Clean the database

# Look for injected JavaScript in post content
SELECT ID, post_title FROM wp_posts
WHERE post_content LIKE '%<script%eval%>%';

Steps — Prevention

  • Keep WordPress core, themes, and plugins updated at all times.
  • Use strong, unique passwords for all admin accounts.
  • Restrict file execution in the uploads directory:
    # .htaccess in wp-content/uploads/
    <Files *.php>
      deny from all
    </Files>
  • Install a firewall plugin (Wordfence or Sucuri).
  • Enable two-factor authentication for admin accounts.
  • Schedule regular automated backups (UpdraftPlus to remote storage).

Verify

Scan with Wordfence or Sucuri SiteCheck. Submit the site to Google Search Console for a malware review if it was flagged.

Notes

  • Change all passwords (WordPress admin, FTP, database, hosting panel) after a confirmed infection.
  • Contact your hosting provider — they may have server-level malware scanning and can help identify the entry point.

Fix WordPress Widgets Not Editable

If WordPress widgets appear grayed out, cannot be expanded, or refuse to save changes, the issue is typically caused by a JavaScript error, a plugin conflict, or a corrupted widget configuration in the database.

Prerequisites

  • WordPress admin access
  • Browser developer tools (F12)

Steps

1. Check the browser console for JavaScript errors

Open Appearance → Widgets, then press F12 to open developer tools and click the Console tab. Look for red JavaScript errors — they usually identify the conflicting script.

2. Test with all plugins disabled

Rename the plugins folder temporarily via FTP or SSH:

mv /var/www/html/wp-content/plugins /var/www/html/wp-content/plugins.bak

Reload the Widgets page. If widgets become editable, rename the folder back and reactivate plugins one by one to find the culprit.

3. Switch to a default theme

Go to Appearance → Themes and activate Twenty Twenty-Four or another default WordPress theme. If widgets work, the issue is in the active theme.

4. Reset the widget options in the database

As a last resort, delete the corrupted widget option:

wp option delete sidebars_widgets

This resets all widget assignments — you will need to re-add widgets to sidebars.

Verify

Open Appearance → Widgets and click a widget to expand it. You should be able to edit and save widget settings.

Notes

  • The block-based Widgets editor (introduced in WordPress 5.8) replaced the legacy drag-and-drop interface. Some older themes or plugins that hook into the legacy system can cause conflicts with the new editor.
  • Install the Classic Widgets plugin to restore the legacy widget interface if the block editor causes persistent issues.

Scale WordPress Databases with Remote Servers and Replication

As WordPress traffic grows, the database often becomes the bottleneck. This guide covers separating the database to a remote server, adding read replicas, and using HyperDB or similar tools to distribute queries.

Prerequisites

  • A dedicated database server with MySQL/MariaDB
  • SSH access to both the web server and database server
  • WordPress with a plugin supporting multi-database (HyperDB or LudicrousDB)

Steps

1. Move the database to a dedicated server

# On the web server — export the database
wp db export backup.sql

# Copy to the database server
scp backup.sql user@db-server:/tmp/

# On the database server — import
mysql -u root -p wordpress_db < /tmp/backup.sql

# Grant remote access to the WordPress user
mysql -u root -p -e "GRANT ALL ON wordpress_db.* TO 'wp_user'@'web-server-IP' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;"

2. Update wp-config.php on the web server

define( 'DB_HOST', 'db-server-IP' );
define( 'DB_USER', 'wp_user' );
define( 'DB_PASSWORD', 'password' );
define( 'DB_NAME', 'wordpress_db' );

3. Set up MySQL replication (primary → replica)

# On the primary DB server — enable binary logging in my.cnf
[mysqld]
log_bin = mysql-bin
server-id = 1

# On the replica
[mysqld]
server-id = 2

# On the primary — create replication user
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'replica-IP' IDENTIFIED BY 'replpassword';

# On the replica
CHANGE MASTER TO
  MASTER_HOST = 'primary-IP',
  MASTER_USER = 'replicator',
  MASTER_PASSWORD = 'replpassword',
  MASTER_LOG_FILE = 'mysql-bin.000001',
  MASTER_LOG_POS = 0;
START SLAVE;

4. Configure WordPress to use read replicas

Install LudicrousDB (a fork of HyperDB) and configure it to send SELECT queries to read replicas and writes to the primary:

// db-config.php
$wpdb->add_database( array(
    'host'     => 'primary-IP',
    'user'     => 'wp_user',
    'password' => 'password',
    'name'     => 'wordpress_db',
    'write'    => 1,
    'read'     => 0,
) );
$wpdb->add_database( array(
    'host'     => 'replica-IP',
    'user'     => 'wp_user',
    'password' => 'password',
    'name'     => 'wordpress_db',
    'write'    => 0,
    'read'     => 1,
) );

Verify

Check replication status on the replica: SHOW SLAVE STATUS\GSeconds_Behind_Master should be near 0. Confirm WordPress loads correctly with the new database configuration.

Notes

  • Replication lag can cause stale reads immediately after a write — ensure cache invalidation is aware of this.
  • Amazon Aurora MySQL and Google Cloud SQL offer managed replication without manual configuration.

Improve WordPress Website Security

WordPress security involves multiple layers: keeping software updated, hardening configuration, restricting access, and monitoring for intrusions. This guide covers the most impactful security measures.

Prerequisites

  • WordPress admin access
  • SSH/FTP access to the server
  • A current backup before making changes

Steps

1. Keep everything updated

Enable automatic background updates for minor WordPress releases in wp-config.php:

define( 'WP_AUTO_UPDATE_CORE', true );

Keep all plugins and themes updated via Dashboard → Updates or auto-updates.

2. Use strong passwords and two-factor authentication

Install Two Factor or Google Authenticator plugin for admin accounts. Enforce strong passwords via a plugin or the built-in WordPress password strength meter.

3. Limit login attempts

Install Limit Login Attempts Reloaded or configure fail2ban at the server level to block brute-force attacks.

4. Change the WordPress table prefix

The default wp_ prefix is targeted by SQL injection scripts. Change it during installation or via a migration tool.

5. Disable file editing from the admin

// wp-config.php
define( 'DISALLOW_FILE_EDIT', true );

6. Protect wp-config.php and .htaccess

# .htaccess — deny access to wp-config.php
<Files wp-config.php>
  order allow,deny
  deny from all
</Files>

7. Disable XML-RPC if not needed

add_filter( 'xmlrpc_enabled', '__return_false' );

8. Install a security plugin

Wordfence or Sucuri Security provide a web application firewall, malware scanning, and login security in a single plugin.

9. Use HTTPS

Install a free SSL certificate via Let's Encrypt (certbot) and force HTTPS using an .htaccess redirect or server-level configuration.

10. Regular backups

Schedule automated backups with UpdraftPlus to remote storage (S3, Google Drive, Dropbox).

Verify

Run a security scan with Wordfence or Sucuri SiteCheck. Address any flagged issues.

Notes

  • Security is an ongoing process — schedule monthly reviews of user accounts, plugin versions, and security logs.
  • Remove any inactive themes and plugins — they are still executable even when not active.

Allow TimThumb to Access wp-content

TimThumb is a PHP image resizing script used by some older WordPress themes. When denied access to wp-content, it cannot resize images and returns errors. This guide covers the fix using Apache's .htaccess directives.

Prerequisites

  • Apache web server
  • FTP or SSH access to add/edit .htaccess files

Steps

1. Identify the blocking rule

Check the .htaccess file in wp-content/ for a deny from all directive:

cat /var/www/html/wp-content/.htaccess

2. Add an allow rule for TimThumb

Edit (or create) /var/www/html/wp-content/.htaccess:

<Files "timthumb.php">
  Order Allow,Deny
  Allow from all
</Files>

If the restriction is on a specific subdirectory:

# In the theme's .htaccess or in the directory containing timthumb.php
Order Allow,Deny
Allow from all

Verify

Load a page that uses TimThumb-resized images. Images should now display correctly without 403 errors in the browser console.

Notes

  • TimThumb has known security vulnerabilities (remote file inclusion). If you are using it, ensure you are running the latest version and have the ALLOW_EXTERNAL setting disabled in timthumb.php.
  • Modern themes use WordPress's built-in image resizing (add_image_size()) instead of TimThumb — consider migrating away from TimThumb if possible.

Migrate WordPress Multisite to Single Site + WooCommerce

Migrating a subsite from a WordPress Multisite network to a standalone WordPress install (which may also include WooCommerce) requires exporting content, cleaning up multisite-specific data, and remapping URLs and uploads paths.

Prerequisites

  • WP-CLI installed on both the source and destination servers
  • A fresh WordPress install on the destination server
  • FTP/SSH access to both environments

Steps

1. Export the subsite from Multisite

# Export a specific subsite (replace BLOG_ID with the subsite's ID)
wp --url=https://network.example.com/subsite export --dir=/tmp/export/ --skip-comments

2. Export the subsite database tables

# Multisite uses site-specific tables like wp_2_posts, wp_2_postmeta, etc.
# Export those tables
mysqldump -u root -p wordpress_db   wp_2_posts wp_2_postmeta wp_2_terms wp_2_term_taxonomy   wp_2_term_relationships wp_2_options wp_2_comments wp_2_commentmeta   > /tmp/subsite-export.sql

3. Import and rename tables on the destination

# Import
mysql -u root -p wordpress_single < /tmp/subsite-export.sql

# Rename tables (remove the site ID prefix)
RENAME TABLE wp_2_posts TO wp_posts;
RENAME TABLE wp_2_postmeta TO wp_postmeta;
# ... repeat for all tables

4. Search-replace the old URL

wp search-replace 'https://network.example.com/subsite' 'https://newsite.example.com' --skip-columns=guid

5. Copy uploads

# Multisite uploads are stored per-site under blogs.dir or sites/
rsync -av /var/www/html/wp-content/blogs.dir/2/files/           /var/www/html/new-site/wp-content/uploads/

6. Reinstall WooCommerce and import products

After confirming the base WordPress migration works, install WooCommerce on the destination and run the WooCommerce setup wizard. Product data should already be present if exported from the multisite database.

Verify

Browse all migrated pages, posts, and WooCommerce products. Check that image URLs resolve correctly and no URLs still point to the old network domain.

Notes

  • WooCommerce stores some settings in the wp_options table with serialized data — wp search-replace handles serialized data correctly.
  • After migration, run WooCommerce → Status → Tools → Regenerate product lookup tables to rebuild WooCommerce internal indexes.

Delete Unattached Images Using WP-CLI

Over time, WordPress media libraries accumulate images that are not attached to any post (uploads that were never used or orphaned when posts were deleted). WP-CLI can identify and delete these in bulk.

Prerequisites

  • WP-CLI installed and configured
  • SSH access to the server
  • A backup of the database and wp-content/uploads/

Steps

1. Preview unattached attachments (dry run)

# List all unattached attachments (post_parent = 0)
wp post list --post_type='attachment' --post_parent=0 --format=table --fields=ID,post_title,guid

2. Delete all unattached attachments

# Delete all unattached attachments and their files
wp post delete   $(wp post list --post_type='attachment' --format=ids --post_parent=0)   --force

3. Delete by MIME type (more targeted)

# Delete only unattached JPEG images
wp post delete   $(wp post list --post_type='attachment' --format=ids     --post_parent=0 --post_mime_type='image/jpeg')   --force

# Delete only unattached PNG images
wp post delete   $(wp post list --post_type='attachment' --format=ids     --post_parent=0 --post_mime_type='image/png')   --force

Verify

# Confirm no unattached attachments remain
wp post list --post_type='attachment' --post_parent=0 --format=count

The count should be 0 (or reflect only intentionally unattached files you chose to keep).

Notes

  • The --force flag permanently deletes the post record and the physical file from disk — this cannot be undone.
  • Some images may be unattached (post_parent=0) but still referenced in page content via URL. Always do a visual check or a content search before mass deleting.
  • For very large media libraries, process in batches: add --posts-per-page=100 --paged=1 to the list command and loop through pages.

Disable Scroll Wheel Zoom on Embedded Google Maps

Embedded Google Maps iframes intercept mouse scroll events, causing the map to zoom instead of letting the user scroll the page. This guide shows how to disable scroll zoom using a CSS overlay and optional JavaScript for a better user experience.

Prerequisites

  • An embedded Google Map iframe on a WordPress page
  • Access to theme CSS or the page editor

Steps

Option A — CSS pointer-events overlay

Wrap the iframe in a container div and use CSS to intercept scroll events:

<!-- HTML wrapper -->
<div class="map-wrap">
  <div class="map-overlay"></div>
  <iframe
    src="https://www.google.com/maps/embed?pb=..."
    width="600" height="450"
    style="border:0;" allowfullscreen="" loading="lazy">
  </iframe>
</div>
/* CSS */
.map-wrap {
  position: relative;
}
.map-overlay {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  z-index: 2; /* sits above the iframe, blocking scroll */
}
.map-wrap iframe {
  display: block;
  width: 100%;
}
/* Allow interaction when user clicks the map */
.map-wrap.clicked .map-overlay {
  display: none;
}

Option B — Click-to-enable JavaScript

document.addEventListener('DOMContentLoaded', function () {
  var mapWraps = document.querySelectorAll('.map-wrap');
  mapWraps.forEach(function (wrap) {
    wrap.addEventListener('click', function () {
      this.classList.add('clicked');
    });
    // Re-enable overlay when user scrolls away
    document.addEventListener('scroll', function () {
      wrap.classList.remove('clicked');
    }, { passive: true });
  });
});

Verify

Load the page and scroll over the map area. The page should scroll normally. Click the map to activate it — scroll should then zoom the map.

Notes

  • If using Google Maps JavaScript API (not an iframe), you can disable scroll zoom directly: set scrollwheel: false in the map options object.
  • The click-to-enable approach is recommended for accessibility — it gives users explicit control over when map interaction is enabled.

Configure WordPress Behind an AWS Application Load Balancer

When WordPress sits behind an AWS Application Load Balancer (ALB) configured for SSL termination, the ALB handles HTTPS but communicates with WordPress over HTTP. This causes WordPress to generate HTTP URLs for assets and mixed-content warnings. The fix involves trusting the X-Forwarded-Proto header.

Prerequisites

  • WordPress running on EC2 or ECS behind an ALB
  • ALB configured with an HTTPS listener that forwards to HTTP on the backend
  • Access to wp-config.php

Steps

1. Add the HTTPS detection code to wp-config.php

Place this code before the line /* That's all, stop editing! */:

// Trust the X-Forwarded-Proto header from the ALB
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
     && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS']       = 'on';
    $_SERVER['SERVER_PORT'] = 443;
}

2. Set the WordPress and Site URLs to HTTPS

define( 'WP_HOME',    'https://yourdomain.com' );
define( 'WP_SITEURL', 'https://yourdomain.com' );

3. Configure ALB health checks

In the ALB target group settings:

  • Set the health check path to /wp-login.php or a lightweight endpoint.
  • Set the expected HTTP response code to 200.

4. Optionally add an Nginx redirect for direct HTTP access

If instances are also accessible directly (not just via the ALB), add this to the Nginx server block:

if ($http_x_forwarded_proto != "https") {
    return 301 https://$host$request_uri;
}

Verify

Access the site via the ALB's HTTPS endpoint. All page resources (CSS, JS, images) should load over HTTPS with no mixed-content warnings in the browser console.

Notes

  • Only trust the X-Forwarded-Proto header from known load balancers — never from arbitrary clients. ALB injects this header so it is safe in this context.
  • If using multiple ALBs or CloudFront in front of the ALB, ensure each layer forwards the original protocol header.

Fix "Table wp_postmeta Was Not Locked with LOCK TABLES"

The error Table 'wp_postmeta' was not locked with LOCK TABLES occurs during a MySQL database import when a previous table's dump included a LOCK TABLES statement but was not followed by an UNLOCK TABLES before the next table's data. This leaves MySQL in a locked state.

Prerequisites

  • MySQL command-line or phpMyAdmin access
  • The SQL dump file you are importing

Steps

Option A — Fix the dump file

Open the SQL dump file in a text editor and locate the section before the failing table. Look for a LOCK TABLES without a matching UNLOCK TABLES:

# Problematic dump structure:
LOCK TABLES `wp_posts` WRITE;
-- ... INSERT statements ...
-- UNLOCK TABLES; <-- missing!

LOCK TABLES `wp_postmeta` WRITE;  <-- error occurs here

Add the missing UNLOCK TABLES; line after the last INSERT for the preceding table:

LOCK TABLES `wp_posts` WRITE;
-- ... INSERT statements ...
UNLOCK TABLES;   <-- added

LOCK TABLES `wp_postmeta` WRITE;

Option B — Export a clean dump with proper unlocks

Re-export the database with mysqldump using options that ensure proper locking syntax:

mysqldump -u root -p   --single-transaction   --add-locks   --complete-insert   wordpress_db > clean-export.sql

Verify

Re-run the import. The error should not reappear and all tables should import successfully.

Notes

  • The --single-transaction flag in mysqldump uses a transaction instead of table locks, which is safer for InnoDB tables and avoids this class of locking errors.
  • phpMyAdmin exports sometimes produce incomplete SQL when interrupted — always verify the dump file size against what is expected.

Configure WordPress Behind a Load Balancer

When WordPress runs behind a load balancer that handles SSL termination, WordPress receives requests over HTTP and may generate incorrect HTTP URLs. The fix is to detect the forwarded protocol header and set the HTTPS server variable accordingly.

Prerequisites

  • WordPress installed on one or more web servers behind a load balancer
  • Access to wp-config.php and the web server configuration

Steps

1. Update wp-config.php

Add before the stop editing comment:

if ( 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
    $_SERVER['HTTPS']       = 'on';
    $_SERVER['SERVER_PORT'] = 443;
}

2. Configure Nginx to redirect HTTP to HTTPS

server {
    listen 80;
    server_name example.com www.example.com;

    # Redirect direct HTTP access to HTTPS
    if ($http_x_forwarded_proto != "https") {
        return 301 https://$host$request_uri;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

3. Configure Apache to redirect HTTP to HTTPS

# In VirtualHost or .htaccess
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]

4. Set WordPress URLs to HTTPS

define( 'WP_HOME',    'https://example.com' );
define( 'WP_SITEURL', 'https://example.com' );

Verify

Access the site via the load balancer. Open browser developer tools and confirm no mixed-content warnings appear. Check that WordPress generates all asset URLs with https://.

Troubleshooting

  • Redirect loop: Ensure the redirect condition checks HTTP_X_FORWARDED_PROTO (not just port 80) to avoid infinite redirects when the load balancer forwards on port 80 with the HTTPS header.
  • WordPress still generates HTTP URLs: Confirm $_SERVER['HTTPS'] is set to 'on' by adding a temporary var_dump($_SERVER['HTTPS']) in a test file.

Notes

  • Only trust X-Forwarded-Proto when requests come from a known, trusted load balancer — not from arbitrary clients on the internet.
  • For AWS ALB specifically, the header is always set by the ALB and cannot be spoofed by clients.