Why WordPress Doesn’t Send Emails After the 6.9 Update (Fix & Solutions)

Why WordPress Doesn’t Send Emails After the 6.9 Update

If your WordPress emails stopped working after the 6.9 update and you’re struggling to understand why, you’re in the right place to fix this issue step by step. Many site owners noticed contact form emails, WooCommerce notifications, password resets, and admin alerts failing or going to spam.

This step‑by‑step guide shows exactly how to fix WordPress email issues using SMTP, the most reliable solution recommended for WordPress 6.9 and beyond.

Why SMTP Is Mandatory After WordPress 6.9

WordPress 6.9 introduced stricter handling of the email envelope sender. As a result:

  • PHP mail() is no longer reliable
  • Emails without authentication fail silently
  • Gmail, Outlook, and Yahoo block unauthenticated senders

SMTP fixes all of this by authenticating emails before sending.

Step 1: Choose a Reliable SMTP Plugin

For WordPress 6.9, these plugins work best:

  • WP Mail SMTP (Beginner‑friendly)
  • FluentSMTP (Free & powerful)
  • Post SMTP Mailer (Advanced logging)

Recommended for most sites: WP Mail SMTP

Step 2: Install WP Mail SMTP Plugin

  1. Go to WordPress Dashboard → Plugins → Add New
  2. Search for WP Mail SMTP
  3. Click InstallActivate

Once activated, you’ll see the WP Mail SMTP Setup Wizard.

Step 3: Choose Your SMTP Mailer

WP Mail SMTP supports multiple mailers. Popular options:

Mailer Best For
Gmail / Google Workspace Small sites, blogs
Brevo (Sendinblue) Marketing + transactional emails
SendLayer WooCommerce & business sites
Amazon SES High‑volume sending
Other SMTP Custom hosting SMTP

For beginners, choose Gmail or Brevo.

Step 4: Configure SMTP Settings (Example: Other SMTP)

If you select Other SMTP, enter:

  • SMTP Host: yourmailprovider.com
  • Encryption: TLS
  • SMTP Port: 587
  • Authentication: ON
  • Username: your email address
  • Password: email app password

Enable Force From Email Enable Force From Name

Step 5: Fix the “From Email Address” Issue (Very Important)

After WordPress 6.9, emails sent from:

[email protected]

are often rejected.

Best Practice:

Make sure this email exists in your hosting or SMTP provider.

Step 6: Send a Test Email

  1. Go to WP Mail SMTP → Tools → Email Test
  2. Enter your email address
  3. Click Send Email

If successful, WordPress email issues are fixed If failed, continue to DNS setup below

Step 7: Add SPF, DKIM & DMARC Records (Critical for Deliverability)

Without DNS authentication, emails may still land in spam.

Required DNS Records:

SPF (Example)

v=spf1 include:_spf.google.com ~all

DKIM

Provided by your SMTP provider (copy exactly)

DMARC (Basic Setup)

v=DMARC1; p=none; rua=mailto:[email protected]

Add these in Domain DNS Manager (GoDaddy, Namecheap, Cloudflare, etc.)

Step 8: Fix Contact Form & WooCommerce Emails

Contact Forms (CF7 / Elementor / WPForms)

  • Set From Email to same SMTP email
  • Avoid user‑entered emails as sender

WooCommerce

Go to: WooCommerce → Settings → Emails

  • Enable email notifications
  • Ensure “From email” matches SMTP email

Step 9: Enable Email Logging (Optional but Recommended)

WP Mail SMTP Pro includes email logs.

Free alternatives: – WP Mail Logging – FluentSMTP logs

This helps track: – Failed emails – SMTP errors – Delivery status

Common Errors & Fixes After WordPress 6.9

SMTP Authentication Failed

  • Check username/password
  • Use app password (Gmail)

Emails Go to Spam

  • Add SPF, DKIM, DMARC
  • Avoid free Gmail as sender

Emails Not Sending at All

  • Disable other SMTP plugins
  • Check hosting mail restrictions

Final Checklist (WordPress 6.9 Email Fix)

  • SMTP plugin installed
  • Authenticated domain email
  • Correct From Email
  • SPF, DKIM, DMARC added
  • Test email successful

Need Expert Help?

If you’re still facing issues with WordPress email delivery, SMTP errors, or WooCommerce notifications, Technocrackers can help.

Why Every Web Agency Needs a White Label WordPress Partner in 2026

Introduction: The Scaling Dilemma Every Agency Faces

If you run a web or SEO agency, you’ve probably been here before —too many client projects, not enough developers, and an inbox full of “when will my site go live?” messages.We get it. Agencies grow fast, and web development capacity often struggles to keep up. But hiring full-time developers isn’t always the answer — it’s expensive, unpredictable, and hard to scale without sacrificing profit margins.That’s exactly where a white-label WordPress partner comes in — especially one who lives and breathes Elementor.

What’s Changing in 2026

2026 is shaping up to be the year of agency specialization.Most agencies are niching down, focusing on SEO, paid media, or design — not on the tech-heavy parts of WordPress builds.At the same time, client expectations are higher than ever:

  • “Can you deliver it by next week?”
  • “Can you make it faster on mobile?”
  • “Can you integrate this with our CRM and optimize for SEO?”

Agencies that can say yes confidently to those requests — without stretching their team — are the ones that’ll win.

Why White Label Partnerships Make Sense (Now More Than Ever)

Here’s the truth: agencies that scale sustainably all have one thing in common — they know what to keep in-house and what to delegate.When you partner with a white-label WordPress team like ours, you instantly:

  1. Expand your team capacity without hiring.
  2. We become your development wing — working behind the scenes, branded as you.
  3. Deliver faster using Elementor workflows optimized for agencies.
  4. We use pre-built kits, reusable widgets, and automation to cut build times.
  5. Keep your margins healthy.
  6. No surprise payroll costs. No office overhead. Just predictable, per-project pricing.
  7. Protect your client relationships.
  8. We work under strict NDAs — your clients will only ever see your brand.

Real-World Example

One of our agency partners in New York came to us after burning through three in-house developers in a year.They were great at SEO and client management — but every WordPress project slowed them down.We set up a white-label collaboration:

  • Shared Slack channel
  • Trello for project management
  • Standardized Elementor templates

In the first quarter, they delivered 12 websites instead of 5. Their profit margins grew by 38%, and client satisfaction scores hit a record high.That’s not a one-off. We see this story play out every month with agencies across the U.S.

The Hidden Advantage: Strategic Focus

Outsourcing your builds doesn’t just save time — it frees up focus.When your team isn’t bogged down in revisions and debugging, they can double down on client strategy, retention, and upselling — the real growth drivers.A white-label partner becomes your invisible engine — running smoothly in the background while you lead the brand forward.

Why Agencies Choose TechnoCrackers

We’ve built over 300+ WordPress and Elementor websites as a silent partner for agencies.Our secret sauce? Reliability + speed + communication.

  • Dedicated Slack channels for every agency
  • Elementor Pro expertise for rapid, high-quality builds
  • SEO-friendly site structures from day one
  • White-label reporting and project documentation

When we say we’re your team — we mean it.

Ready to Scale Without Hiring?

Let’s talk about how our white-label Elementor team can help your agency scale profitably in 2026.

Schedule a Discovery Call

How to Create Custom Product Tabs in WooCommerce

How to Create Custom Product Tabs in WooCommerce

Creating custom product tabs in WooCommerce using Advanced Custom Fields (ACF) allows store owners to display additional product information in a clean, organized, and user-friendly way. Instead of hardcoding content, ACF enables you to manage tab titles, content, and order directly from the WordPress admin panel, making it easy to create unlimited, product-specific tabs without touching code repeatedly.

Step 1: Install Required Plugins

Make sure these plugins are active:

  • WooCommerce
  • Advanced Custom Fields (ACF) (Free or Pro)

Step 2: Create ACF Fields

Option A: Single Custom Tab

  1. Go to ACF → Field Groups → Add New
  2. Field Group Title: Product Custom Tabs
  3. Add Fields:
    • Tab Title
      • Field Type: Text
      • Field Name: tab_title
    • Tab Content
      • Field Type: WYSIWYG Editor
      • Field Name: tab_content
  1. Location Rule:
    • Post Typeis equal toProduct
  2. Publish the field group

Option B: Multiple Tabs (Recommended – ACF Repeater)

  1. Add a field:
    • Field Type: Repeater
    • Field Label: Product Tabs
    • Field Name: product_tabs
  2. Inside the repeater, add sub-fields:
    • Tab Title
      • Type: Text
      • Name: tab_title
    • Tab Content
      • Type: WYSIWYG
      • Name: tab_content
    • Tab Priority (Optional)
      • Type: Number
      • Name: tab_priority
  1. Location Rule:
    • Post Typeis equal toProduct
  2. Publish

Multiple Tabs

Step 3: Add PHP Code to Display ACF Tabs

Add this code to your child theme functions.php or a custom plugin.

add_filter( 'woocommerce_product_tabs', 'acf_product_tabs' );
function acf_product_tabs( $tabs ) {
    if ( have_rows( 'product_tabs' ) ) {
        $i = 1;
        while ( have_rows( 'product_tabs' ) ) {
            the_row();
            $title    = get_sub_field( 'tab_title' );
            $priority = get_sub_field( 'tab_priority' ) ?: (50 + $i);
            if ( $title ) {
                $tabs['acf_tab_' . $i] = array(
                    'title'    => esc_html( $title ),
                    'priority' => intval( $priority ),
                    'callback' => function() use ( $i ) {
                        acf_product_tab_content( $i );
                    }
                );
            }
            $i++;
        }
    }
    return $tabs;
}

Step 4: Add Callback Function for Tab Content

function acf_product_tab_content( $index ) {
    if ( have_rows( 'product_tabs' ) ) {
        $i = 1;
        while ( have_rows( 'product_tabs' ) ) {
            the_row();
            if ( $i === $index ) {
                echo '<div class="acf-product-tab-content">';
                the_sub_field( 'tab_content' );
                echo '</div>';
                break;
            }
            $i++;
        }
    }
}

Step 5: Add Tabs from Product Edit Screen

  1. Edit any WooCommerce product
  2. Scroll to Product Tabs section
  3. Click Add Row
  4. Enter:
    • Tab Title
    • Tab Content
    • Optional Priority
  5. Update the product

Tabs from Product Edit Screen

Tabs now appear automatically on the frontend.

Tabs-frontend

Final Thoughts

Creating custom product tabs in WooCommerce using Advanced Custom Fields (ACF) is a powerful and flexible way to manage detailed product information without hardcoding content. By leveraging ACF repeater fields and WooCommerce hooks, you can add unlimited, product-specific tabs that are easy to maintain directly from the WordPress admin panel.

This approach improves content organization, enhances the user experience, and allows non-technical users to update product details effortlessly. Whether you’re adding FAQs, specifications, warranty information, or size guides, ACF-based product tabs provide a scalable solution that works seamlessly with any WooCommerce theme or page builder.

By implementing this method, you gain full control over tab structure, content, and display logic—making it an ideal choice for modern, content-rich WooCommerce stores.

Want custom WooCommerce product tabs and advanced features built the right way?

Let TechnoCrackers handle your full WordPress development needs — from custom builds to seamless integrations, contact us today.

How to Make Elementor Tabs Auto Change Like a Slider

Elementor Tabs Auto Change Like a Slider

Elementor Tabs are great for organizing content, but by default they require users to click each tab manually. If you want a more dynamic experience—similar to a slider or carousel—you can make Elementor tabs auto-change at regular intervals. This approach improves engagement, highlights multiple pieces of content, and keeps your layout visually active.

In this article, we’ll explain what auto-changing tabs are, why they’re useful, and the different ways you can achieve this effect in Elementor.

What Does “Auto-Changing Tabs” Mean?

Auto-changing tabs behave like a slider:

  • Tabs switch automatically after a set time (for example, every 3–5 seconds)
  • Content rotates without user interaction
  • Users can still click tabs manually if they want

This creates a smooth, animated experience similar to testimonials sliders, feature showcases, or step-by-step presentations.

Plugins Required

Before creating auto-changing tabs in Elementor, make sure the following plugins are installed and activated on your WordPress website:

  • Elementor – Page builder plugin
  • Elementor Pro – Required for advanced features and better control
  • ElementsKit – Provides the Advanced Tabs widget with enhanced functionality

Step 1: Create the Tabs Layout

Follow these steps to create the tab structure using ElementsKit:

  1. Open the page where you want to add the tabs.
  2. Click Edit with Elementor.
  3. In the Elementor editor panel, search for ElementsKit
  4. Drag and drop the Advanced Tab widget (from ElementsKit) onto your page.

  5. Choose the tab layout based on your design needs — Vertical or Horizontal.
  6. Add your tab titles and corresponding tab content for each tab.
  7. Design the tabs to match your website layout by adjusting:
    • Colors
    • Typography
    • Spacing
    • Icons and alignment
  8. Open the Advanced settings of the widget.
  9. Add a custom CSS class to the tab widget (this will be used later to enable auto-changing functionality).

Step 2: Convert Tabs to Auto-Change Like a Slider

Now that your tabs are created and styled, the next step is to make them auto-change automatically like a slider. This is done using a custom JavaScript script that switches tabs at a fixed time interval.

This method works smoothly with ElementsKit Advanced Tabs and uses Bootstrap’s native tab functionality for better compatibility.

What This Script Does

  • Automatically switches tabs every 3 seconds
  • Works with vertical or horizontal tabs
  • Stops auto-sliding when a user manually clicks a tab
  • Targets the widget using the custom CSS class you added earlier

How to Use the Script

  1. Copy the script provided below.
  2. Paste it into one of the following locations:
    • Your theme’s php file (before </body>)
    • A Custom Code plugin (recommended)
    • Elementor → Custom Code → Location: Footer
  3. Save or publish the changes.
  4. Refresh the page and check the front end — your tabs should now auto-slide like a slider.
<script>
document.addEventListener("DOMContentLoaded", function () {
  document.querySelectorAll('.verticle-tabbing').forEach(wrapper => {
    const tabs = wrapper.querySelectorAll('.elementkit-nav-link');
    if (tabs.length < 2) return;
    let currentIndex = 0;
    let interval = null;
    function showTab(tab) {
      // Bootstrap 5
      if (window.bootstrap && bootstrap.Tab) {
        bootstrap.Tab.getOrCreateInstance(tab).show();
      }
      // Bootstrap 4 fallback
      else if (window.jQuery && jQuery.fn.tab) {
        jQuery(tab).tab('show');
      }
    }
    function startAutoplay() {
      if (interval) return;
      interval = setInterval(() => {
        showTab(tabs[currentIndex]);
        currentIndex = (currentIndex + 1) % tabs.length;
      }, 3000);
    }
    function stopAutoplay() {
      if (!interval) return;
      clearInterval(interval);
      interval = null;
    }
    // Wait for ElementsKit & Bootstrap to load
    setTimeout(startAutoplay, 1200);
    // Stop autoplay on manual tab click
    tabs.forEach(tab => {
      tab.addEventListener('click', stopAutoplay);
    });
  });
});
</script>

Final Thoughts

Making Elementor tabs auto change like a slider is a powerful way to enhance interactivity and engagement on your website. Whether you use custom JavaScript, an Elementor add-on, or a slider-based alternative, this technique can help present more content in less space—while keeping your design modern and dynamic.

With the right timing and thoughtful UX, auto-changing tabs can turn static layouts into engaging visual experiences.

If you’re planning a custom WordPress website or need advanced Elementor functionality without relying on heavy plugins, getting expert guidance early can save time and rework later.

You can Book a Free WordPress Consultation with our team to discuss your requirements, explore the best technical approach, and ensure your site is built for performance and scalability.

How to Build a Custom WooCommerce Product Template with Category based Sorting & Pagination in Elementor

If you’re looking to take full control of how your WooCommerce products appear on your store, Elementor Pro makes it incredibly easy. With custom templates, loop items, and dynamic WooCommerce elements, you can design a fully branded shop page — no coding required.

In this guide, you’ll learn how to:

  • Create a custom WooCommerce archive template
  • Build a custom product loop layout with images, pricing, meta info & add-to-cart
  • Enable pagination, column control, and product count
  • Add category-based sorting functionality for a better user experience

By the end of this tutorial, you’ll have a professional, flexible product layout that matches your brand perfectly.

Step 1: Create a Custom Product Archive Template

To start customizing the default WooCommerce Shop and Category pages, follow these steps:

  1. Go to WordPress Dashboard → Templates → Theme Builder
  2. Click Add New → Archive Template
  3. Choose Product Archive
  4. Give the template a name (example: “Custom Shop Layout”)
  5. Click Create Template

Once loaded, you’ll see Elementor’s default WooCommerce widgets.

Now customize the layout using:

  • Heading or Page Title
  • Breadcrumbs
  • Search Bar
  • Sorting & Filter Options (optional)

Step 2: Create a Custom Product Loop Item

Elementor Pro now offers Loop Grid and Loop Item builder — allowing full control over how each product card looks.

  1. Add the Loop Grid widget to your archive page
  2. Click Create Template
  3. This opens a new “Loop Item” layout canvas

Now build your product card using Elementor widgets:

Include the following elements:

Element Widget
Product Image Product Image
Product Title Product Title
Short Description Product Short Description
SKU Product SKU (Dynamic Tags → Woo Meta)
Categories Product Terms
Price Product Price
Add to Cart Add to Cart Button

Style spacing, borders, typography and hover effects as needed.

Tip: You can make the product image & name clickable by enabling the Link to Product Page option.

Once done → Save & Publish.

Step 3: Set Columns, Layout & Pagination

Back in the Archive template:

  1. Click on the Loop Grid widget
  2. Set:
  • Columns (Desktop/Tablet/Mobile)
  • Number of Items per Page
  • Select Loop Template you created earlier
  • Enable Pagination

Pagination options include:

  • Numeric Pagination
  • Load More Button
  • Infinite Scroll (Pro)

Step 4: Apply the Template to Shop & Category Pages

Before closing the editor:

  1. Click Publish
  2. Set Display Conditions:

All Product Archives
✔ Shop Page
✔ Product Categories

Click Save.

Your custom layout is now live!

Step 5: Add Category-Based Sorting to Your WooCommerce Store

By default, WooCommerce lets customers sort products by price, popularity, rating or date — but not by the category order you set. To make browsing easier and more organized, we’ll add a Category-Based Sorting option to both:

  • The WooCommerce Sorting Dropdown
  • The Default Sorting Setting inside Appearance → Customize

This ensures your products appear in the exact order of their assigned categories — perfect for structured stores like machinery, spare parts, collections, or apparel.

Add Custom Sorting Code

Add the following code to your functions.php or a custom plugin (recommended if using a child theme):

/**
 * Add "Sort by Category Order" option to WooCommerce sorting dropdown.
 */
add_filter( 'woocommerce_get_catalog_ordering_args', 'woolist_catalog_orderby_category_order_args' );
function woolist_catalog_orderby_category_order_args( $args ) {
    $orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : apply_filters(
        'woocommerce_default_catalog_orderby',
        get_option( 'woocommerce_default_catalog_orderby' )
    );

    if ( 'category_order' === $orderby_value ) {
        // Use pre_get_posts instead of the_posts (pagination-safe)
        add_action( 'pre_get_posts', 'woolist_pre_get_products_by_category', 999 );
    }

    return $args;
}

/**
 * Add label in dropdown menu.
 */
add_filter( 'woocommerce_default_catalog_orderby_options', 'woolist_add_category_order_option' );
add_filter( 'woocommerce_catalog_orderby', 'woolist_add_category_order_option' );
function woolist_add_category_order_option( $sortby ) {
    $sortby['category_order'] = __( 'Sort by Category Order', 'woolist' );
    return $sortby;
}

/**
 * Sort WooCommerce products by category order (pagination-safe).
 */
function woolist_pre_get_products_by_category( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }

    if ( ! ( is_shop() || is_product_category() ) ) {
        return;
    }

    // Prevent infinite recursion
    remove_action( 'pre_get_posts', 'woolist_pre_get_products_by_category', 999 );

    // 1. Get categories in manual order (respect term_order)
    $get_terms_recursive = function( $parent = 0 ) use ( &$get_terms_recursive ) {
        $ordered = [];
        $terms = get_terms([
            'taxonomy'   => 'product_cat',
            'parent'     => $parent,
            'hide_empty' => false,
            'orderby'    => 'term_order',
            'order'      => 'ASC',
        ]);
        foreach ( $terms as $term ) {
            $ordered[] = $term;
            $children = $get_terms_recursive( $term->term_id );
            if ( ! empty( $children ) ) {
                $ordered = array_merge( $ordered, $children );
            }
        }
        return $ordered;
    };

    $categories = $get_terms_recursive( 0 );
    if ( empty( $categories ) ) {
        return;
    }

    // 2. Assign priority per category
    $priority = 1;
    $cat_priority = [];
    foreach ( $categories as $cat ) {
        $cat_priority[ $cat->term_id ] = $priority++;
    }

    // 3. Fetch all product IDs first (ignore pagination)
    $args = $query->query_vars;
    $args['fields'] = 'ids';
    $args['posts_per_page'] = -1;
    $args['paged'] = 0;
    $all_products = get_posts( $args );

    // 4. Get priority for each product
    $get_priority = function( $product_id ) use ( $cat_priority ) {
        $terms = wp_get_post_terms( $product_id, 'product_cat', ['fields' => 'ids'] );
        if ( empty( $terms ) ) {
            return PHP_INT_MAX;
        }
        $priorities = [];
        foreach ( $terms as $t ) {
            if ( isset( $cat_priority[ $t ] ) ) {
                $priorities[] = $cat_priority[ $t ];
            }
        }
        return ! empty( $priorities ) ? min( $priorities ) : PHP_INT_MAX;
    };

    // 5. Sort all product IDs by category, then menu_order, then title
    usort( $all_products, function( $a, $b ) use ( $get_priority ) {
        $pa = $get_priority( $a );
        $pb = $get_priority( $b );
        if ( $pa !== $pb ) return $pa - $pb;

        $ma = (int) get_post_field( 'menu_order', $a );
        $mb = (int) get_post_field( 'menu_order', $b );
        if ( $ma !== $mb ) return $ma - $mb;

        return strcasecmp( get_the_title( $a ), get_the_title( $b ) );
    });

    // 6. Manual pagination slicing
    $per_page = $query->get( 'posts_per_page' );
    $paged = max( 1, $query->get( 'paged' ) );
    $offset = ( $paged - 1 ) * $per_page;
    $paged_products = array_slice( $all_products, $offset, $per_page );

    // 7. Update query vars for WooCommerce
// Get full, sorted list of product IDs
$query->set( 'post__in', $all_products );
$query->set( 'orderby', 'post__in' );
// DO NOT do array_slice or manual slicing
    $query->set( 'posts_per_page', $per_page );
    $query->set( 'paged', $paged );

    // 8. Correct pagination count
    add_filter( 'found_posts', function( $found, $q ) use ( $all_products ) {
        if ( $q->is_main_query() && ( is_shop() || is_product_category() ) ) {
            return count( $all_products );
        }
        return $found;
    }, 10, 2 );
}

Step 6: Enable It from WooCommerce Customizer

Once the code is added successfully, go to: Appearance → Customize → WooCommerce → Product Catalog → Default Product Sorting

You’ll now see a new option:  Sort by Category Order

Select it — and from now on, your shop and category pages will automatically display products grouped and ordered by the category hierarchy.

 (Optional) Organize Categories Manually

If you want full drag-and-drop control over category ordering, install:

Category Order and Taxonomy Terms Order Plugin
 https://wordpress.org/plugins/taxonomy-terms-order/  (Free from WordPress.org)

This plugin allows you to reorder product categories visually, and the sorting function will respect that order automatically.

Step 7: Test Your Sorting & Ensure Everything Works

Now that your custom product template and category-based sorting are in place, it’s important to verify everything is functioning correctly.

Go to:  YourSite.com/shop

Check the following:

  • Products display in the correct category-based sequence
  • The layout matches your Elementor loop template
  • Pagination works (example: clicking page 2 continues the ordered list)
  • Sorting dropdown shows “Sort by Category Order”

Try switching between sorting options:

  • Default Sorting
  • Price (asc/desc)
  • Category Order

Then return to Sort by Category Order to ensure your custom order re-applies correctly.

Final Result

If everything is configured correctly, your WooCommerce shop will now:

  • Display products grouped and ordered by category hierarchy
  • Use a fully custom Elementor loop layout
  • Support pagination, sorting, and filtering
  • Allow you to change category order easily without editing loops
  • Automatically update sorting whenever you rearrange categories

Conclusion

Congratulations — you now have a fully customized WooCommerce product archive with:

  • A unique Elementor product card design
  • Pagination and responsive layout control
  • Smart category-based sorting
  • Optional manual drag-and-drop category ordering

This setup gives your WooCommerce store a clean, organized, and intuitive structure — especially useful for businesses with large catalogs, product families, or grouped collections.

If you’re planning a WooCommerce store that requires custom product templates, category-based sorting, or advanced pagination in Elementor, our WordPress developers can build a fast, scalable solution tailored to your business needs.

You can Book a Free Project Consultation to discuss your store requirements, performance goals, and the best development approach.

How to Generate Shareable Preview Links for Draft Posts, Pages, and CPTs in WordPress without any plugins

When working on a WordPress website—whether you’re a developer, content creator, or agency—it’s common to share draft pages or posts with clients before publishing.

However, by default, WordPress requires users to be logged in to view draft content. This can be inconvenient when you simply want to share a preview link with a client or team member who doesn’t have WordPress access.

Most people install plugins to do this, but you can generate secure, shareable preview links without any plugin using a small custom code snippet.

In this guide, you’ll learn how to:

  • Allow non-logged-in users to view draft previews
  • Generate a shareable URL for any post type
  • Automatically display a “Sharable Link” meta box in the WordPress editor
  • Add one-click buttons to copy or open the preview link
  • Support posts, pages, and unlimited custom post types (CPTs)

Why Do You Need Shareable Draft Links?

Here are common scenarios where this is extremely useful:

  • Sending preview links to clients
  • Allowing team members to review draft content
  • Sharing landing pages or funnels under development
  • Getting approval on blog posts, pages, or custom post types
  • Avoiding unnecessary WordPress accounts or login steps

How the Solution Works

We will:

  • Add a URL parameter key=guest
  • Modify the main query to allow draft preview when this key is present
  • Add a meta box that generates the shareable link
  • Automatically show the live URL for published posts
  • Allow preview URL for draft/unpublished posts
  • Add “Copy Link” and “Open Link” buttons

Full Code: Shareable Preview Link Generator

Place this code in your child theme’s functions.php File.

Code Snippet:

add_action('pre_get_posts', 'allow_draft_preview');
function allow_draft_preview($query) {
    if (isset($_GET['key']) && $_GET['key'] == 'guest') {
        if ($query->is_main_query()) {
            $query->set('post_status', array('publish', 'draft'));
        }
    }
}
add_action('add_meta_boxes', function () {
// Define your target post types
$custom_post_types = ['post', 'page', 'people', 'locations', 'practiceareas', 'alerts', 'blog', 'events', 'news', 'publications', 'video', 'open-positions'];
foreach ($custom_post_types as $post_type) {
        if (post_type_exists($post_type)) {
            add_meta_box(
                'sharable_link_box',
                'Sharable Link',
                'render_sharable_link_box',
                $post_type,
                'side',
                'high'
            );
       }
    }
}, 20); // Priority 20 ensures CPTs are registered

function render_sharable_link_box($post) {
    $is_published = $post->post_status === 'publish';
    if ( $is_published ) {
        $url = esc_url( get_permalink( $post->ID ) );
        $label = 'Live URL';
    } else {
        $preview_url = get_preview_post_link( $post->ID );
        $url = esc_url( add_query_arg( 'key', 'guest', $preview_url ) );
        $label = 'Preview URL';
    }
    echo '<label style="font-weight: bold; display: block; margin-bottom: 5px;">' . esc_html( $label ) . '</label>';
    echo '<input type="text" readonly value="' . $url . '" style="width:100%; font-size:12px;" id="sharableLinkURL">';
    echo '<div style="margin-top:8px; display:flex; gap:8px; flex-wrap:wrap;">';
    echo '<button type="button" class="button" onclick="copySharableURL()">Copy Link</button>';
    echo '<button type="button" class="button button-secondary" onclick="openSharableURL()">Open in New Tab</button>';
    echo '</div>';

// JS for buttons
echo <<<HTML
<script>
function copySharableURL() {
    const input = document.getElementById('sharableLinkURL');
    input.select();
    input.setSelectionRange(0, 99999); // For mobile
    document.execCommand('copy');
    alert('URL copied to clipboard!');
}
function openSharableURL() {
    const input = document.getElementById('sharableLinkURL');
    const url = input.value;
    window.open(url, '_blank');
}
</script>
HTML;
}

How to Use the Shareable Link

Once the code is active:

1. Edit any post, page, or CPT

You’ll see a new meta box on the right side labeled “Sharable Link”.

2. For published posts

It shows the Live URL.

3. For draft or pending posts

It shows a secure preview URL like:

https://yoursite.com/?p=123&preview=true&key=guest

4. Share it with your client

They do not need to log in.

5. Use the built-in buttons

  • Copy Link → copies URL to clipboard
  • Open in New Tab → loads preview instantly


Conclusion

With just a few lines of code, you can easily generate shareable preview links for any WordPress post type—without plugins.

If you’re building a WordPress site that requires custom workflows, advanced CMS logic, or plugin-free solutions, our team can take complete ownership of the project—from planning and development to testing and launch.

You can Book a Free Project Consultation to discuss your requirements, timelines, and how we can deliver the entire solution for you.

How to Bulk Update Image File Names and titles in WordPress Without Breaking Page Content for SEO purpose

How to Bulk Update Image File Names and titles in WordPress Without Breaking Page Content for SEO purpose

Images play a major role in SEO. When your image file names and titles are optimized correctly, search engines can better understand your content — helping you rank higher in Google Image Search and improving overall keyword relevance.

But updating image file names manually can be risky. One wrong step and your pages may break, leaving broken image links across your website.

The good news? You can bulk update image file names and titles safely in WordPress using a powerful tool: Phoenix Media Rename.

In this guide, you’ll learn:

  • Why optimized image names help SEO
  • How to rename images safely without breaking pages
  • How Phoenix Media Rename automatically updates all internal links
  • Best practices for SEO-friendly file names
  • Tips for bulk renaming efficiently

Step 1: Install Phoenix Media Rename

  • Go to Plugins → Add New
  • Search “Phoenix Media Rename”
  • Install & activate

Step 2: Bulk Rename Images (Individually or in Groups)

  • Go to Media → Library → List View
  • You will see a new rename field next to each image
  • Update the file name and title
  • Then select Rename & Retitle
  • Then click on apply for update

Add update image name

Benefits of Phoenix Media Rename for SEO

  • Boosts image search visibility
  • Fixes messy media libraries
  • Prevents broken URLs
  • Keeps all page layouts intact
  • Helps search engines understand your content better
  • Speeds up images optimization

Conclusion

Renaming image file names is one of the simplest yet most powerful SEO improvements you can make — but it must be done safely.
With Phoenix Media Rename, you can bulk rename image files and update titles across your entire WordPress site without breaking a single link.

If you’re serious about improving SEO, cleaning up your media names is a highly effective step.

If you’re managing a WordPress site with hundreds (or thousands) of images and need bulk SEO-friendly updates without risking content or rankings, our team can handle the entire process—from planning and execution to validation and rollout.

You can Schedule a Free Consultation to discuss your site structure, SEO concerns, and how we can safely implement large-scale WordPress changes for you.

How to Create a Custom Rich Text Editor in Gravity Forms for Better Formatting and User Experience

How to Create a Custom Rich Text Editor in Gravity Forms for Better Formatting and User Experience

Gravity Forms is one of the most powerful form builders available for WordPress — but it has one limitation:

It does not offer a built-in Rich Text Editor (WYSIWYG) field.

For websites that rely on user-generated content—such as blogs, directories, reviews, job listings, or frontend post submissions—a basic textarea simply isn’t enough.

A proper Rich Text Editor allows users to format text with:

  • Headings (H1–H6)
  • Links & Media
  • Bold, Italic, Underline
  • Bullet and Numbered Lists
  • Images
  • Blockquotes
  • Inline styles like alignment, colors, and spacing

So in this tutorial, you’ll learn how to add a fully functional Rich Text Editor field inside Gravity Forms using custom code — without installing an extra plugin.

What This Solution Supports

  • Works on frontend
  • Uses TinyMCE (WordPress default editor)
  • Supports media uploads
  • Outputs clean formatted HTML
  • Works with Gravity Forms Post Creation Add-On
  • Prevents raw HTML from showing in visual mode

Why You Need a Custom Rich Text Editor in Gravity Forms

A normal textarea field cannot store or render formatted content like:

  • Titles and headings
  • Styled paragraphs
  • Embedded media or images
  • Links and structured lists

If you’re building forms like:

  • Blog submission form
  • Directory or listing submission
  • User reviews
  • Job listing submission
  • Knowledgebase article submission
  • Community-generated content

…then users need a proper editor — not a plain text field.

Step-by-Step Guide

Step 1 — Add This Code to Your functions.php

Copy and paste the following entire code block into your theme’s functions.php or a custom plugin:

( Code already formatted, so no changes needed — paste exactly as provided.)

// Enable a fully functional Rich Text Editor field in Gravity Forms
// Works on frontend + shows formatted output (not HTML tags)

//Enqueue editor scripts (TinyMCE + Media)
add_action('gform_enqueue_scripts', function() {
    wp_enqueue_editor();
    wp_enqueue_media();
});

// 1. Add "Rich Text Editor" button to Form Builder

add_filter('gform_add_field_buttons', function($field_groups){
    $field_groups[] = array(
        'name'   => 'custom_fields',
        'label'  => __('Custom Fields', 'gravityforms'),
        'fields' => array(
            array(
                'class' => 'button',
                'value' => __('Rich Text Editor', 'gravityforms'),
                'data-type' => 'editor',
            ),
        ),
    );
    return $field_groups;
});

// 2. Register field type name

add_filter('gform_field_type_title', function($type){
    return $type === 'editor' ? __('Rich Text Editor', 'gravityforms') : $type;
});

// 3. Add standard settings (optional)
add_action('gform_field_standard_settings', function($position, $form_id){
    if($position == 25){ ?>
        <li class="field_setting">
            <label for="field_placeholder">
                <?php esc_html_e("Placeholder", "gravityforms"); ?>
                <?php gform_tooltip("form_field_placeholder"); ?>
            </label>
            <input type="text" id="field_placeholder" class="fieldwidth-3" size="35"
                onkeyup="SetFieldProperty('placeholder', this.value);" />
        </li>
    <?php }
}, 10, 2);

// 4. Editor JS for Gravity Form Builder
add_action('gform_editor_js', function(){ ?>
<script type='text/javascript'>
    fieldSettings.editor = '.label_setting, .description_setting, .css_class_setting, .placeholder_setting, .visibility_setting, .required_setting';
</script>
<?php });

// 5. Render the TinyMCE editor on frontend

add_filter('gform_field_input', function($input, $field, $value, $lead_id, $form_id){
    if($field->type !== 'editor') return $input;

    ob_start();
    wp_editor(
        $value,
        'input_' . $form_id . '_' . $field->id,
        array(
            'textarea_name' => 'input_' . $field->id,
            'media_buttons' => true,
            'quicktags'     => true,
            'tinymce'       => array(
                'height' => 300,
                'toolbar1' => 'formatselect bold italic underline | bullist numlist blockquote | alignleft aligncenter alignright link unlink | removeformat',
            ),
        )
    );
    $editor_html = ob_get_clean();

    return sprintf(
        '<div class="ginput_container ginput_container_editor">%s</div>',
        $editor_html
    );
}, 10, 5);

// Safe rendering decode and render formatted HTML (not tags)

if ( ! function_exists( 'gf_rich_allowed_tags' ) ) {
    function gf_rich_allowed_tags() {
        return array(
            'p'      => array(),
            'br'     => array(),
            'strong' => array(),
            'b'      => array(),
            'em'     => array(),
            'i'      => array(),
            'u'      => array(),
            'span'   => array( 'style' => true, 'class' => true ),
            'div'    => array( 'style' => true, 'class' => true ),
            'a'      => array( 'href' => true, 'title' => true, 'target' => true, 'rel' => true ),
            'h1'     => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(),
            'ul'     => array(), 'ol' => array(), 'li' => array(),
            'blockquote' => array(),
            'img'    => array( 'src' => true, 'alt' => true, 'width' => true, 'height' => true ),
            'strong' => array(), 'em' => array()
        );
    }
}

/* ---------- Allow safe inline CSS properties ---------- */
add_filter( 'safe_style_css', function( $styles ) {
    $custom = array(
        'text-decoration',
        'color',
        'background-color',
        'font-size',
        'font-weight',
        'font-style',
        'text-align',
        'line-height',
        'font-family',
    );
    return array_unique( array_merge( $styles, $custom ) );
});

/* ---------- Helper to decode + sanitize editor HTML ---------- */
if ( ! function_exists( 'gf_render_editor_html_safe' ) ) {
    function gf_render_editor_html_safe( $raw ) {
        if ( $raw === null || $raw === '' ) {
            return '';
        }

        // 1) Decode HTML entities (handles encoded "<" as &lt;)
        $decoded = html_entity_decode( (string) $raw, ENT_QUOTES | ENT_HTML5, 'UTF-8' );

        // 2) Fix possible double-encoding like &amp;lt;
        if ( strpos( $decoded, '&amp;lt;' ) !== false || strpos( $decoded, '&amp;gt;' ) !== false ) {
            $decoded = str_ireplace( array( '&amp;lt;', '&amp;gt;' ), array( '<', '>' ), $decoded );
            $decoded = html_entity_decode( $decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
        }

        // 3) Sanitize with allowed tags (keeps <a href>, <span style>, etc.)
        return wp_kses( $decoded, gf_rich_allowed_tags() );
    }
}

/* ---------- Merge-tag replacement: decode + render editor fields (priority 5) ---------- */
add_filter( 'gform_replace_merge_tags', function( $text, $form, $entry, $url_encode, $esc_html, $nl2br ) {
    if ( empty( $form ) || empty( $entry ) ) {
        return $text;
    }

    // decode the container text early (handles already-encoded merge outputs)
    $text = html_entity_decode( (string) $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );

    foreach ( $form['fields'] as $field ) {
        if ( isset( $field->type ) && $field->type === 'editor' ) {
            $id  = $field->id;
            $raw = rgar( $entry, $id );

            // render the raw field value safely (allows links, styles)
            $rendered = gf_render_editor_html_safe( $raw );

            // Replace merge tags: {ID} and {Label:ID} and {Label:ID:modifier}
            $text = str_replace( '{' . $id . '}', $rendered, $text );

            $pattern = '/\{[^}]*:' . preg_quote( $id, '/' ) . '(?::[^}]*)?\}/';
            $text = preg_replace( $pattern, $rendered, $text );
        }
    }

    // Final pass: ensure we haven't left encoded common tags; decode conservative patterns
    if ( strpos( $text, '&lt;' ) !== false || strpos( $text, '&amp;lt;' ) !== false ) {
        $search = array(
            '&lt;br /&gt;','&lt;br&gt;','&lt;br/&gt;',
            '&lt;p&gt;','&lt;/p&gt;',
            '&lt;h1&gt;','&lt;/h1&gt;','&lt;h2&gt;','&lt;/h2&gt;','&lt;h3&gt;','&lt;/h3&gt;',
            '&lt;h4&gt;','&lt;/h4&gt;','&lt;h5&gt;','&lt;/h5&gt;','&lt;h6&gt;','&lt;/h6&gt;',
            '&lt;strong&gt;','&lt;/strong&gt;','&lt;b&gt;','&lt;/b&gt;',
            '&lt;em&gt;','&lt;/em&gt;','&lt;i&gt;','&lt;/i&gt;','&lt;u&gt;','&lt;/u&gt;',
            '&lt;span&gt;','&lt;/span&gt;','&lt;div&gt;','&lt;/div&gt;',
            '&lt;a href=&quot;','&lt;/a&gt;',
            '&amp;lt;','&amp;gt;'
        );
        $replace = array(
            '<br />','<br>','<br/>',
            '<p>','</p>',
            '<h1>','</h1>','<h2>','</h2>','<h3>','</h3>',
            '<h4>','</h4>','<h5>','</h5>','<h6>','</h6>',
            '<strong>','</strong>','<b>','</b>',
            '<em>','</em>','<i>','</i>','<u>','</u>',
            '<span>','</span>','<div>','</div>',
            '<a href="','</a>',
            '<', '>'
        );

        $text = str_ireplace( $search, $replace, $text );
    }

    // Finally sanitize entire result again with allowed tags (keeps a href + span style)
    $text = wp_kses( $text, gf_rich_allowed_tags() );

    return $text;
}, 5, 6 );


/**
 * 3) Ensure gform_get_input_value returns safe rendered HTML for editor fields.
 *    Many templates/plugins call this directly to get the field value.
 */
add_filter( 'gform_get_input_value', function( $value, $entry, $field, $input_id ) {
    if ( is_object( $field ) && isset( $field->type ) && $field->type === 'editor' ) {
        return gf_render_editor_html_safe( $value );
    }
    return $value;
}, 10, 4 );

/**
 * 4) Admin Entry Detail try to force rendered HTML on entry detail page
 */
add_filter( 'gform_entry_field_value', function( $value, $field, $entry ) {
    if ( is_object( $field ) && isset( $field->type ) && $field->type === 'editor' ) {
        return gf_render_editor_html_safe( $value );
    }
    return $value;
}, 10, 3 );

/**
 * 5) Shortcode: [gf_editor entry="123" field="2"]
 *    Use this in templates/content to safely print the rendered editor field.
 */
if ( ! shortcode_exists( 'gf_editor' ) ) {
    add_shortcode( 'gf_editor', function( $atts ) {
        $atts = shortcode_atts( array(
            'entry' => '',
            'field' => '',
        ), $atts, 'gf_editor' );

        $entry_id = intval( $atts['entry'] );
        $field_id = intval( $atts['field'] );

        if ( $entry_id <= 0 || $field_id <= 0 ) return '';

        $entry = GFAPI::get_entry( $entry_id );
        if ( is_wp_error( $entry ) ) return '';

        $raw = rgar( $entry, $field_id );
        return gf_render_editor_html_safe( $raw );
    } );
}

Once added, Gravity Forms will display a new field called:  Rich Text Editor under the “Custom Fields” tab.

 

Step 2 — Create a New Gravity Form

  1. Go to Forms → New Form
  2. Give it a name (Example: Blog Submission Form)
  3. Add standard fields like:
    • Name
    • Email
    • Post Title
  4. Add your new field: Rich Text Editor
  5. Save the form

Tip: Note the Field ID for later (Example: Field ID: 10).

Step 3 — Configure Gravity Forms Post Creation Add-On

  1. Go to Form → Settings → Post Creation
  2. Create a new feed
  3. Settings example:
Setting Example
Post Type Post (or Custom Post Type)
Status Draft (recommended for testing)
Author Logged-in User
Title {Post Title:1}
Content {Description:10} ← must match Rich Editor Field ID

(Optional) Map:

  • Categories
  • Tags
  • Featured Image
  • Custom fields

Important: Ensure the content field uses the field ID of the Rich Text Editor.

Step 4 — Add the Form to a Page

Use this shortcode anywhere:
[ gravityform id="123" title="true" description="false" ajax="true" ]

Replace 123 with your form ID.

Publish your page (example: Submit a Blog).

Step 5 — Test the Submission

  1. Open the form on the frontend
  2. Format content using the editor (bold, headings, images, lists, etc.)
  3. Submit the form
  4. Check:

Posts → All Posts

You should see a new post created — with formatting preserved.

  1. In WP Admin go to Posts → All Posts and find the new post (status based on your feed setting).
  2. Click Edit on the post and check the Visual tab in the editor — the content should render as formatted HTML (not raw HTML tags).
  3. View the post on the frontend and confirm headings, lists, images and styling display correctly.

Final Result

You now have a fully working frontend content editor inside Gravity Forms — without additional plugins.

  • Works with WordPress media uploader
  • Supports formatting, links, images, headings
  • Perfect for guest posting, user-generated content, and custom submission workflows

Conclusion

Adding a rich text editor to Gravity Forms dramatically improves the user experience for forms involving long-form content.

Whether you’re building a submission portal, directory, review system, or frontend publishing workflow — this upgrade makes Gravity Forms behave more like a real blogging platform.

If your project requires custom Gravity Forms functionality, enhanced user experience, or plugin-free custom editors, our team can handle the complete development—from planning and implementation to testing and launch.

You can Book a Free Project Consultation to discuss your requirements, timelines, and how we can deliver a scalable, high-quality solution for your website.

Contact us

Let's Unleash Your Digital Potential Together.

Address

C-605, Ganesh glory 11, Nr. BSNL Office, Jagatpur Road, S.G. Highway, Jagatpur, Ahmedabad, India - 382481.

Phone

INDIA : (091) 8200639242 USA : +1 (310) 868-6009

Limited Time Offer

X

Try a Free 2-Hour Test Task

Experience our quality, speed, and communication on any small WordPress task before you commit. No contract. No cost. No obligation.
[For New Agency Partners]

"*" indicates required fields

Name*