Ajax gives 400 error

Explanation:

The issue you are likely facing is that you are trying to add your ajax actions from within the widget itself that may be instantiated on the elementor/widgets/widgets_registered hook.

If so, this is hook elementor/widgets/widgets_registered is too late and do_action( "wp_ajax_{$action}" ) and do_action( "wp_ajax_nopriv_{$action}" ) have already fired.

Working example:

NOTE: very rough and not thoroughly abstracted, which I will leave to you, this code however exemplifies creating a widget that outputs a button that when clicked, calls a callback, then appends a result to the target element, in this case “Hello World!”

Create a plugin with the following three files:

  • plugin.php
  • widget.php
  • script.php

plugin.php

<?php

/**
 * Plugin Name: Custom Elementor Widget
 * Plugin URI:  https://example.com
 * Description: Custom Elementor Widget
 * Version:     1.0.0
 * Author:      WordPress StackExchange
 * Author URI:  https://example.com
 */

final class Elementor_Custom_Extension
{
    private static $_instance = null;

    public function __construct()
    {
        add_action('plugins_loaded', [$this, 'init']);
    }

    public static function instance()
    {
        if (is_null(self::$_instance)) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    public static function dir_path($path="")
    {
        return plugin_dir_path(__FILE__) . $path;
    }

    public static function dir_url($path="")
    {
        return plugin_dir_url(__FILE__) . $path;
    }

    public function init()
    {
        add_action('elementor/widgets/widgets_registered', [$this, 'widget_init']);
        add_action('elementor/frontend/after_register_scripts', [$this, 'widget_scripts']);
        add_action('wp_ajax_pgggo_widget', [$this,  'widget_callback']);
        add_action('wp_ajax_nopriv_pgggo_widget', [$this,  'widget_callback']);
    }

    public function widget_callback()
    {
        wp_send_json_success([
            'message' => 'Hello World!'
        ]);
    }

    public function widget_scripts()
    {
        $script_url  = Elementor_Custom_Extension::dir_url('script.js');
        $script_path = Elementor_Custom_Extension::dir_path('script.js');

        wp_register_script('pgggo_widget', $script_url, ['jquery'], filectime($script_path));
        wp_localize_script('pgggo_widget', 'pgggo_widget_vars', [
            'ajax_url' => admin_url('admin-ajax.php'), 
            'ajax_action' => 'pgggo_widget',
        ]);
    }

    public function widget_init()
    {
        require_once(__DIR__ . '/widget.php');

        \Elementor\Plugin::instance()->widgets_manager->register_widget_type(
            new Elementor_Custom_Widget()
        );
    }
}

Elementor_Custom_Extension::instance();

widget.php

<?php

class Elementor_Custom_Widget extends \Elementor\Widget_Base {
    public function get_name() {
        return 'pgggo_widget';
    }

    public function get_title() {
        return __( 'My Custom Widget', 'pgggo_widget' );
    }

    public function get_icon() {
        return 'fa fa-code';
    }

    public function get_categories() {
        return [ 'general' ];
    }

    protected function _register_controls() {
        // add your controls
    }

    protected function render() {

        wp_enqueue_script( 'pgggo_widget' );

        $settings = $this->get_settings_for_display();

        ?>

        <div id="my-custom-widget">

            <button>Click Me</button>

            <div class="target">

            </div>
        </div>

        <?php

    }
}

script.js

jQuery(document).ready(function($) {
    $('#my-custom-widget').on('click', 'button', function(event) {
        event.preventDefault();
        $.ajax({
            url: pgggo_widget_vars.ajax_url,
            type: 'GET',
            data: {
            'action': pgggo_widget_vars.ajax_action,
            // 'nonce' : // do your nonce here...
            },
            success: function (response) {
                $('#my-custom-widget .target').append(response.data.message);
            },
        });
    });
});   

Update:

In relation to comment your 518351

if you notice there is a problem widget_callback() in plugin.php will not have access to widget.php $settings = $this->get_settings_for_display() so it will not be able to get the data for that specific widget

If you need to pass data to the back-end from the front-end via AJAX, you need to make the data accessible to your AJAX handler. To do so you can add a call to wp_localize_script in your widget render() method that localises the the data.

Example:

protected function render() {

    $settings = $this->get_settings_for_display();

    wp_enqueue_script( 'pgggo_widget' );

    // make data accessible on the JavaScript variable "pgggo_widget_settings"
    wp_localize_script( 'pgggo_widget', 'pgggo_widget_settings', [
        'settings' => $settings
    ] );

    // etc...
}

Then in your JavaScript:

var settings = pgggo_widget_settings.settings;
console.log(settings.some_property);

WARNING!

In the example above I am passing the entire settings object to the wp_localize_script function call. You may NOT want to do this for security purposes in the event you have any sensitive information in your settings that you don’t want exposed. In that case, be sure to selectively push data from the $this->get_settings_for_display() method call onto your local variable.