How to Add Google reCAPTCHA to Elementor v4 Atomic Forms (Step-by-Step)

An atomic form recaptcha is currently missing from the default Elementor 4.0 widget toolkit. If you have recently upgraded your website and started building forms with the new React-based Atomic Editor, you probably noticed a major issue. Many users have taken to forums like Reddit to ask why they cannot find the Google reCAPTCHA field type anymore, and why there is no mention of it in the official documentation.

This post solves this exact problem. Because Elementor 4.0 has decoupled forms into modular, independent widgets (such as individual input blocks, labels, and submit buttons inside a container), the old way of selecting a recaptcha “field type” from a dropdown is gone.

Here are the 2 best ways to bring this essential security feature back into your custom layouts.

Why Is the Atomic Form reCAPTCHA Missing in Elementor 4.0?

In legacy versions of Elementor, forms were handled by a single, self-contained widget. When you needed to block spam, you simply added a new item to your form fields repeater and set its type to “reCAPTCHA”.

When building with the new Atomic Editor, you will quickly realize that adding an atomic form recaptcha is not as straightforward as it used to be. Because each field is now its own isolated widget, the form container itself does not hold the recaptcha settings. To secure your forms, you must insert a dedicated widget wrapper inside your Atomic Form container. This custom element is responsible for loading the necessary API scripts, outputting a hidden input token, and sending that token to your server for validation.


Method 1: Adding Atomic Form reCAPTCHA via Custom Code

If you want to keep your site lightweight and avoid installing additional third-party tools, you can add a custom integration manually. To do this, you can register a basic widget that hooks into the new layout system.

Atomic Form Recaptcha
How To Add Google Recaptcha To Elementor V4 Atomic Forms (Step-By-Step) 3

How to implement your custom code:

  1. Access your WordPress site files using an FTP client or your hosting file manager.
  2. Navigate to your active child theme folder and open the functions.php file.
  3. Paste your custom Elementor widget registration and server-side validation script at the bottom of the file. (This script should register a new widget within the native atomic-form category and hook into the elementor_pro/forms/validation filter to process tokens).
  4. Save your changes.

Once the script is active, your custom widget will appear directly inside the Elementor editor under the Atomic Form widgets section.

<?php
/**
 * Atomic Form reCAPTCHA v3 for Elementor
 * Ported to functions.php
 *
 * Adds a draggable reCAPTCHA widget to your Elementor panel inside the Atomic Form section.
 * Automatically uses Elementor's native keys and supports both v2 and v3.
 */

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

// 1. Register the "Atomic Form" Category if not already registered by parent plugin
add_action( 'elementor/elements/categories_registered', 'register_atomic_recaptcha_category' );

function register_atomic_recaptcha_category( $elements_manager ) {
    $elements_manager->add_category(
        'atomic-form',
        [
            'title' => esc_html__( 'Atomic Form', 'elementor-atomic-recaptcha' ),
            'icon'  => 'eicon-atomic',
        ]
    );
}

// 2. Register the Custom Elementor Widget
add_action( 'elementor/widgets/register', 'register_atomic_recaptcha_widget' );

function register_atomic_recaptcha_widget( $widgets_manager ) {
    
    // Prevent redeclaration errors and ensure class only compiles when Elementor loads this hook
    if ( ! class_exists( 'Elementor_Atomic_Recaptcha_Widget' ) ) {
        
        class Elementor_Atomic_Recaptcha_Widget extends \Elementor\Widget_Base {

            public function get_name() {
                // Retained internal name for backwards compatibility with existing element placements
                return 'atomic_recaptcha_v3'; 
            }

            public function get_title() {
                return esc_html__( 'reCAPTCHA (Atomic)', 'elementor-atomic-recaptcha' );
            }

            public function get_icon() {
                return 'eicon-shield';
            }

            public function get_categories() {
                return [ 'atomic-form' ];
            }

            protected function register_controls() {
                $this->start_controls_section(
                    'content_section',
                    [
                        'label' => esc_html__( 'Settings', 'elementor-atomic-recaptcha' ),
                        'tab'   => \Elementor\Controls_Manager::TAB_CONTENT,
                    ]
                );

                $this->add_control(
                    'recaptcha_type',
                    [
                        'label'   => esc_html__( 'reCAPTCHA Type', 'elementor-atomic-recaptcha' ),
                        'type'    => \Elementor\Controls_Manager::SELECT,
                        'default' => 'v3',
                        'options' => [
                            'v3'           => esc_html__( 'reCAPTCHA v3 (Invisible)', 'elementor-atomic-recaptcha' ),
                            'v2_checkbox'  => esc_html__( 'reCAPTCHA v2 Checkbox', 'elementor-atomic-recaptcha' ),
                            'v2_invisible' => esc_html__( 'reCAPTCHA v2 Invisible', 'elementor-atomic-recaptcha' ),
                        ],
                    ]
                );

                // Pull Elementor's native database integration keys
                $v3_site_key = get_option( 'elementor_pro_recaptcha_v3_site_key', '' );
                $v2_site_key = get_option( 'elementor_pro_recaptcha_site_key', '' );
                
                // V3 Status Messages
                if ( empty( $v3_site_key ) ) {
                    $this->add_control(
                        'setup_warning_v3',
                        [
                            'type' => \Elementor\Controls_Manager::RAW_HTML,
                            'raw'  => '<div style="color: #b91c1c; background: #fee2e2; padding: 12px; border-radius: 4px; font-weight: bold; font-size:12px;">' . 
                                     esc_html__( 'Warning: Google reCAPTCHA v3 keys are missing in Elementor. Please configure them first under Elementor > Settings > Integrations.', 'elementor-atomic-recaptcha' ) . 
                                     '</div>',
                            'condition' => [
                                'recaptcha_type' => 'v3',
                            ],
                        ]
                    );
                } else {
                    $this->add_control(
                        'setup_success_v3',
                        [
                            'type' => \Elementor\Controls_Manager::RAW_HTML,
                            'raw'  => '<div style="color: #15803d; background: #dcfce7; padding: 12px; border-radius: 4px; font-weight: bold; font-size:12px;">' . 
                                     esc_html__( 'Active: Google reCAPTCHA v3 integration detected.', 'elementor-atomic-recaptcha' ) . 
                                     '</div>',
                            'condition' => [
                                'recaptcha_type' => 'v3',
                            ],
                        ]
                    );
                }

                // V2 Status Messages
                if ( empty( $v2_site_key ) ) {
                    $this->add_control(
                        'setup_warning_v2',
                        [
                            'type' => \Elementor\Controls_Manager::RAW_HTML,
                            'raw'  => '<div style="color: #b91c1c; background: #fee2e2; padding: 12px; border-radius: 4px; font-weight: bold; font-size:12px;">' . 
                                     esc_html__( 'Warning: Google reCAPTCHA v2 keys are missing in Elementor. Please configure them first under Elementor > Settings > Integrations.', 'elementor-atomic-recaptcha' ) . 
                                     '</div>',
                            'condition' => [
                                'recaptcha_type' => [ 'v2_checkbox', 'v2_invisible' ],
                            ],
                        ]
                    );
                } else {
                    $this->add_control(
                        'setup_success_v2',
                        [
                            'type' => \Elementor\Controls_Manager::RAW_HTML,
                            'raw'  => '<div style="color: #15803d; background: #dcfce7; padding: 12px; border-radius: 4px; font-weight: bold; font-size:12px;">' . 
                                     esc_html__( 'Active: Google reCAPTCHA v2 integration detected.', 'elementor-atomic-recaptcha' ) . 
                                     '</div>',
                            'condition' => [
                                'recaptcha_type' => [ 'v2_checkbox', 'v2_invisible' ],
                            ],
                        ]
                    );
                }

                // Custom controls for reCAPTCHA v2 Checkbox
                $this->add_control(
                    'v2_theme',
                    [
                        'label'   => esc_html__( 'Theme', 'elementor-atomic-recaptcha' ),
                        'type'    => \Elementor\Controls_Manager::SELECT,
                        'default' => 'light',
                        'options' => [
                            'light' => esc_html__( 'Light', 'elementor-atomic-recaptcha' ),
                            'dark'  => esc_html__( 'Dark', 'elementor-atomic-recaptcha' ),
                        ],
                        'condition' => [
                            'recaptcha_type' => 'v2_checkbox',
                        ],
                    ]
                );

                $this->add_control(
                    'v2_size',
                    [
                        'label'   => esc_html__( 'Size', 'elementor-atomic-recaptcha' ),
                        'type'    => \Elementor\Controls_Manager::SELECT,
                        'default' => 'normal',
                        'options' => [
                            'normal'  => esc_html__( 'Normal', 'elementor-atomic-recaptcha' ),
                            'compact' => esc_html__( 'Compact', 'elementor-atomic-recaptcha' ),
                        ],
                        'condition' => [
                            'recaptcha_type' => 'v2_checkbox',
                        ],
                    ]
                );

                // Custom controls for reCAPTCHA v2 Invisible
                $this->add_control(
                    'v2_badge',
                    [
                        'label'   => esc_html__( 'Badge Position', 'elementor-atomic-recaptcha' ),
                        'type'    => \Elementor\Controls_Manager::SELECT,
                        'default' => 'bottomright',
                        'options' => [
                            'bottomright' => esc_html__( 'Bottom Right', 'elementor-atomic-recaptcha' ),
                            'bottomleft'  => esc_html__( 'Bottom Left', 'elementor-atomic-recaptcha' ),
                            'inline'      => esc_html__( 'Inline', 'elementor-atomic-recaptcha' ),
                        ],
                        'condition' => [
                            'recaptcha_type' => 'v2_invisible',
                        ],
                    ]
                );

                $this->end_controls_section();
            }

            protected function render() {
                $settings = $this->get_settings_for_display();
                $type     = ! empty( $settings['recaptcha_type'] ) ? $settings['recaptcha_type'] : 'v3';

                // Identify site key corresponding to configuration
                $site_key = ( 'v3' === $type ) ? get_option( 'elementor_pro_recaptcha_v3_site_key', '' ) : get_option( 'elementor_pro_recaptcha_site_key', '' );
                
                if ( empty( $site_key ) ) {
                    if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
                        echo '<div style="border: 2px dashed #ef4444; padding: 12px; text-align: center; color: #ef4444; font-size: 13px;">reCAPTCHA keys missing in Elementor > Settings > Integrations</div>';
                    }
                    return;
                }

                // Render visual marker in the active Elementor builder editor canvas
                if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
                    $type_label = esc_html__( 'reCAPTCHA v3 (Active in Background)', 'elementor-atomic-recaptcha' );
                    if ( 'v2_checkbox' === $type ) {
                        $type_label = esc_html__( 'reCAPTCHA v2 (Checkbox)', 'elementor-atomic-recaptcha' );
                    } elseif ( 'v2_invisible' === $type ) {
                        $type_label = esc_html__( 'reCAPTCHA v2 (Invisible)', 'elementor-atomic-recaptcha' );
                    }
                    echo '<div style="border: 2px dashed #0073aa; padding: 12px; text-align: center; color: #0073aa; background: #f0f6fc; font-weight: bold; border-radius: 4px; font-size: 13px;">🛡️ ' . esc_html( $type_label ) . '</div>';
                    return;
                }

                // Front-end structure parameters
                $theme = ! empty( $settings['v2_theme'] ) ? $settings['v2_theme'] : 'light';
                $size  = ! empty( $settings['v2_size'] ) ? $settings['v2_size'] : 'normal';
                $badge = ! empty( $settings['v2_badge'] ) ? $settings['v2_badge'] : 'bottomright';
                ?>
                <div class="atomic-recaptcha-wrapper" 
                     data-type="<?php echo esc_attr( $type ); ?>"
                     data-sitekey="<?php echo esc_attr( $site_key ); ?>"
                     data-theme="<?php echo esc_attr( $theme ); ?>"
                     data-size="<?php echo esc_attr( $size ); ?>"
                     data-badge="<?php echo esc_attr( $badge ); ?>">
                     
                     <!-- Render placeholder target for explicit API load -->
                     <div class="atomic-recaptcha-element"></div>
                     <input type="hidden" name="form_fields[recaptcha_token]" class="atomic-recaptcha-token" value="" />
                     <input type="hidden" name="form_fields[recaptcha_version]" class="atomic-recaptcha-version" value="<?php echo esc_attr( $type ); ?>" />
                </div>
                <?php

                // Script renders only once per page cycle
                static $script_rendered = false;
                if ( ! $script_rendered ) {
                    $script_rendered = true;
                    ?>
                    <script type="text/javascript">
                    document.addEventListener('DOMContentLoaded', function() {
                        
                        function checkAndInitRecaptchas() {
                            const wrappers = document.querySelectorAll('.atomic-recaptcha-wrapper:not([data-initialized])');
                            if (!wrappers.length) return;

                            let hasV3 = false;
                            let v3SiteKey = '';
                            let hasV2 = false;

                            wrappers.forEach(wrapper => {
                                const type = wrapper.getAttribute('data-type');
                                const key = wrapper.getAttribute('data-sitekey');
                                if (type === 'v3') {
                                    hasV3 = true;
                                    v3SiteKey = key;
                                } else if (type === 'v2_checkbox' || type === 'v2_invisible') {
                                    hasV2 = true;
                                }
                            });

                            // Only load API script dynamically if not loaded yet
                            if (!window.grecaptchaLoading && !document.querySelector('script[src*="recaptcha/api.js"]')) {
                                window.grecaptchaLoading = true;
                                let scriptSrc = 'https://www.google.com/recaptcha/api.js';
                                
                                if (hasV3 && v3SiteKey) {
                                    scriptSrc += '?render=' + encodeURIComponent(v3SiteKey);
                                } else if (hasV2) {
                                    scriptSrc += '?render=explicit';
                                }

                                const script = document.createElement('script');
                                script.src = scriptSrc;
                                script.async = true;
                                script.defer = true;
                                document.head.appendChild(script);

                                script.onload = function() {
                                    window.grecaptchaLoading = false;
                                    if (typeof grecaptcha !== 'undefined') {
                                        grecaptcha.ready(initPendingRecaptchas);
                                    }
                                };
                            } else {
                                if (typeof grecaptcha !== 'undefined') {
                                    grecaptcha.ready(initPendingRecaptchas);
                                }
                            }
                        }

                        function initPendingRecaptchas() {
                            const wrappers = document.querySelectorAll('.atomic-recaptcha-wrapper:not([data-initialized])');
                            wrappers.forEach(wrapper => {
                                wrapper.dataset.initialized = "true";
                                wrapper.dataset.isValidated = "false";

                                const type = wrapper.getAttribute('data-type');
                                const siteKey = wrapper.getAttribute('data-sitekey');
                                const tokenField = wrapper.querySelector('.atomic-recaptcha-token');
                                const elementDiv = wrapper.querySelector('.atomic-recaptcha-element');

                                if (!siteKey) return;

                                if (type === 'v3') {
                                    function updateV3Token() {
                                        grecaptcha.execute(siteKey, {action: 'submit'}).then(function(token) {
                                            tokenField.value = token;
                                            wrapper.dataset.isValidated = "true";
                                        });
                                    }
                                    updateV3Token();
                                    setInterval(updateV3Token, 115000); // 115 seconds update cycle (limit is 120s)
                                    
                                } else if (type === 'v2_checkbox') {
                                    const theme = wrapper.getAttribute('data-theme') || 'light';
                                    const size = wrapper.getAttribute('data-size') || 'normal';
                                    
                                    const widgetId = grecaptcha.render(elementDiv, {
                                        'sitekey': siteKey,
                                        'theme': theme,
                                        'size': size,
                                        'callback': function(token) {
                                            tokenField.value = token;
                                            wrapper.dataset.isValidated = "true";
                                        },
                                        'expired-callback': function() {
                                            tokenField.value = '';
                                            wrapper.dataset.isValidated = "false";
                                        },
                                        'error-callback': function() {
                                            tokenField.value = '';
                                            wrapper.dataset.isValidated = "false";
                                        }
                                    });
                                    wrapper.dataset.widgetId = widgetId;
                                    
                                } else if (type === 'v2_invisible') {
                                    const badge = wrapper.getAttribute('data-badge') || 'bottomright';
                                    const form = wrapper.closest('form');
                                    
                                    const widgetId = grecaptcha.render(elementDiv, {
                                        'sitekey': siteKey,
                                        'size': 'invisible',
                                        'badge': badge,
                                        'callback': function(token) {
                                            tokenField.value = token;
                                            wrapper.dataset.isValidated = "true";
                                            
                                            // Trigger form submit action programmatically once validated
                                            if (typeof jQuery !== 'undefined') {
                                                jQuery(form).submit();
                                            } else {
                                                form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
                                            }
                                        },
                                        'expired-callback': function() {
                                            tokenField.value = '';
                                            wrapper.dataset.isValidated = "false";
                                        },
                                        'error-callback': function() {
                                            tokenField.value = '';
                                            wrapper.dataset.isValidated = "false";
                                        }
                                    });
                                    wrapper.dataset.widgetId = widgetId;

                                    if (form) {
                                        // Use standard event capture phase to intercept submits before Elementor's Ajax submits run
                                        form.addEventListener('submit', function(e) {
                                            if (wrapper.dataset.isValidated !== "true") {
                                                e.preventDefault();
                                                e.stopImmediatePropagation();
                                                grecaptcha.execute(widgetId);
                                            }
                                        }, true);
                                    }
                                }
                            });
                        }

                        checkAndInitRecaptchas();
                        // Periodically poll DOM to support dynamic contents, Elementor popups or lazy loaded modules
                        setInterval(checkAndInitRecaptchas, 1000);

                        // If submission attempts fail/succeed on page, automatically reset active v2 fields
                        if (typeof jQuery !== 'undefined') {
                            jQuery(document).on('submit_success submit_error', function() {
                                const activeWrappers = document.querySelectorAll('.atomic-recaptcha-wrapper[data-initialized="true"]');
                                activeWrappers.forEach(wrapper => {
                                    const type = wrapper.getAttribute('data-type');
                                    if (type === 'v2_checkbox' || type === 'v2_invisible') {
                                        const widgetId = wrapper.dataset.widgetId;
                                        if (widgetId !== undefined && typeof grecaptcha !== 'undefined') {
                                            grecaptcha.reset(widgetId);
                                        }
                                        wrapper.dataset.isValidated = "false";
                                        const tokenField = wrapper.querySelector('.atomic-recaptcha-token');
                                        if (tokenField) {
                                            tokenField.value = '';
                                        }
                                    }
                                });
                            });
                        }
                    });
                    </script>
                    <?php
                }
            }
        }
    }

    if ( class_exists( 'Elementor_Atomic_Recaptcha_Widget' ) ) {
        $widgets_manager->register( new Elementor_Atomic_Recaptcha_Widget() );
    }
}

// 3. Handle Form Submission Server-Side Validation
add_action( 'elementor_pro/forms/validation', 'validate_atomic_recaptcha_widget_form', 10, 2 );

function validate_atomic_recaptcha_widget_form( $record, $ajax_handler ) {
    $raw_fields = $record->get_field( [
        'id' => 'recaptcha_token',
    ] );

    if ( empty( $raw_fields ) ) {
        return; // Skip validation if this form instance does not contain the widget wrapper
    }

    $field = current( $raw_fields );
    $token = $field['value'];

    if ( empty( $token ) ) {
        $ajax_handler->add_error( $field['id'], __( 'Spam validation token is missing. Please refresh the page and try again.', 'elementor-atomic-recaptcha' ) );
        return;
    }

    // Determine target version from submitted fields
    $raw_version = $record->get_field( [
        'id' => 'recaptcha_version',
    ] );
    $version_field = ! empty( $raw_version ) ? current( $raw_version ) : null;
    $type          = ( $version_field && ! empty( $version_field['value'] ) ) ? sanitize_text_field( $version_field['value'] ) : 'v3';

    // Extract corresponding site verification secret key
    if ( 'v3' === $type ) {
        $secret_key = get_option( 'elementor_pro_recaptcha_v3_secret_key', '' );
    } else {
        $secret_key = get_option( 'elementor_pro_recaptcha_secret_key', '' );
    }

    if ( empty( $secret_key ) ) {
        return; // Keys missing in DB, bypass to prevent form lockouts
    }

    // Call Google Verification API
    $response = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', [
        'body' => [
            'secret'   => $secret_key,
            'response' => $token,
            'remoteip' => $_SERVER['REMOTE_ADDR'],
        ],
    ] );

    if ( is_wp_error( $response ) ) {
        $ajax_handler->add_error( $field['id'], __( 'Spam verification service temporarily unreachable.', 'elementor-atomic-recaptcha' ) );
        return;
    }

    $body = json_decode( wp_remote_retrieve_body( $response ), true );
    
    if ( ! isset( $body['success'] ) || ! $body['success'] ) {
        $ajax_handler->add_error( $field['id'], __( 'Spam validation check failed. If you are a human, please try again.', 'elementor-atomic-recaptcha' ) );
        return;
    }

    // Version 3 specific score-threshold validation
    if ( 'v3' === $type ) {
        $min_score = (float) get_option( 'elementor_pro_recaptcha_v3_threshold', '0.5' );
        if ( isset( $body['score'] ) && $body['score'] < $min_score ) {
            $ajax_handler->add_error( $field['id'], __( 'Spam validation score too low. If you are a human, please try again.', 'elementor-atomic-recaptcha' ) );
        }
    }
}

For this code block to function, you must enter your Google API keys under Elementor > Settings > Integrations in your WordPress backend. After inserting the snippet, open your editor, navigate to the Atomic Form panel category, and drag the newly created widget into your form layout.

Too much complicated?

let me handle the hard parts so you don’t have to.


Prefer Fiverr? View my profile

Method 2: Using an Atomic Form reCAPTCHA Plugin

For those who want to avoid editing theme files, using a dedicated WordPress plugin is the most reliable path. A plugin-based approach is highly recommended if you need to use different versions of Google’s API or want visual style options inside your editor.

What this plugin method offers:

  • Support for multiple formats: It allows you to choose between reCAPTCHA v2 Checkbox, v2 Invisible, and v3.
  • No code maintenance: You do not have to worry about your custom PHP script breaking during major Elementor core updates.
  • Asynchronous token handling: The plugin manages token refreshes automatically during AJAX submissions, preventing valid users from getting locked out due to expired tokens.

You can download our helper utility plugin below to easily add these capabilities back to your site:

How to Configure and Test Your Atomic Form reCAPTCHA

Once your new atomic form recaptcha widget is live, you need to configure and test it to make sure automated submissions are successfully blocked.

  1. Enter API Keys: Go to your WordPress admin dashboard and navigate to Elementor > Settings > Integrations. Paste your Google site key and secret key into the reCAPTCHA fields.
  2. Edit Your Form: Open your page inside the Elementor 4.0 editor.
  3. Place the Widget: Open the Atomic Form section in the left panel. Drag your recaptcha widget inside the form container, placing it directly above your submit button.
  4. Choose Your Settings: If using a plugin, select your preferred version (v2 or v3) and adjust any styling options.
  5. Publish and Test: Save your page. Open your website in an incognito window, fill out the form, and submit it to ensure the verification works smoothly.
Image 1
How To Add Google Recaptcha To Elementor V4 Atomic Forms (Step-By-Step) 4

Frequently Asked Questions

Does this atomic form recaptcha setup support both v2 and v3?

Yes. While a basic custom PHP script is usually limited to v3, the plugin-based method fully supports both the v2 Checkbox and v2 Invisible options.

Why isn’t this featured in the official Elementor documentation yet?

The React-based Atomic Editor is still a relatively new architecture. Elementor is continually updating their developer resources, but modular forms currently require custom or third-party wrappers to handle specific integrations.

Will this slow down my form submissions?

No. Because validation is handled asynchronously in the background, legitimate users will experience no noticeable delay when submitting their entries.

Table of Contents

Too much complicated?

let me handle the hard parts so you don’t have to.

Prefer Fiverr? View my profile

Leave a Reply

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

Related Posts

9 min read
Find why an Elementor V4 class appears in the editor but is missing or inactive… Read More
12 min read
Fix Elementor Atomic classes that vanish after refresh by tracing save requests, permissions, version mismatches,… Read More
11 min read
Fix an Elementor Core and Pro version mismatch safely, recover dashboard access, verify Pro workflows,… Read More
12 min read
Fix a WordPress critical error when wp-admin is unavailable using Recovery Mode, safe logging, SFTP,… Read More