This is my first attempt to write a plugin and I get an error 400 when trying to use admin-ajax.php. I've looked through some topics both here and in the WordPress support forum, but cannot figure it out.

What I am trying to do is dependant drop-down select. I.e. you chose an option from select #1, then it has to be passed via ajax to populate the second dropdown. To make it simple (for now), everything is on my "frontend" file.

Here is my code:

        <div class="row">
            <select class="cat-select" name="lvl1" id="lvl1">
                <option value="">-- select category --</option>
                foreach ($this->getTopLevelCategories() as $category) {
                    print '<option value="' . $category->term_id . '">' . $category->name . ' (' . $category->count . ')</option>';

        <script type="text/javascript">
            jQuery(document).ready(function () {
                jQuery('#lvl1').change(function () {
                    var lvl1Val = jQuery('#lvl1').val();
                        url: "<?php bloginfo('wpurl'); ?>/wp-admin/admin-ajax.php",
                        method: 'POST',
                        data: {
                            'lvl1' : document.getElementById('lvl1').value
                        success: function(response, status) {
                        error: function(errorThrown){

        <div class="row">
            <select class="cat-select" name="lvl2" id="lvl2">
                <option value="" disabled selected>-- select category lvl2 --</option>


        function getLVl2()
            if (isset($_POST['lvl1'])) :

                $parent = $_POST['lvl1'];

                $option = '<option value="' . $parent . '">';
                $option .= $parent;
                $option .= '</option>';

                echo $option;



        add_action('wp_ajax_getLVl2', 'getLVl2');
        add_action('wp_ajax_nopriv_getLVl2', 'getLVl2');


  1. The code in the function will be changed. What I am trying to do is output the value of the option selected from the first dropdown just to see that it works.
  2. The function and the script will be moved to their places to "keep the code clean"
  • 1
    A 400 error indicates that your AJAX actions are not being registered or registered late, so generally in a theme, you should register them via the functions.php file. Did you put your getLVl2() function and add_action() code in the functions file? Also, have you considered using the REST API instead (which can give you a pretty route/path like my-plugin/v1/getLVl2)?
    – Sally CJ
    Commented Jun 20, 2022 at 7:10
  • No, I don't have them on the template functions.php. I have them in my plugin only. As for the REST API - I've mentioned this is my first attempt to write WordPress plugin so I think it will be more complicated for me than "the usual" way.
    – milenmk
    Commented Jun 20, 2022 at 7:13
  • As for the REST API, you would want to read this, but there are actually built-in endpoints for fetching terms in a specific taxonomy, e.g. /wp/v2/categories for the core category taxonomy. So for example, fetch( '/wp-json/wp/v2/categories' ) would let you, via JS, fetch the site's categories.
    – Sally CJ
    Commented Jun 20, 2022 at 7:36
  • 1
    As for your plugin, where are you calling the add_action()? Is it at the root in the plugin file, e.g. <?php /* Plugin Name: My Plugin */ ... add_action( ... ); ?
    – Sally CJ
    Commented Jun 20, 2022 at 7:39
  • 1
    If you are getting a 0, then that likely means the $_POST['lvl1'] was not set - and you just need to ensure that your JS sends the proper data. So you could just do var_dump( $_POST ); before the if block and you'd know for sure if my guess was correct.
    – Sally CJ
    Commented Jun 20, 2022 at 7:52

1 Answer 1


This may not definitively answer the question, but I hope this helps you in finding the most appropriate solution. And actually, I've tried the super simple plugin at the bottom with your HTML and JS, and everything worked fine for me.

This is my first attempt to write a plugin and I get an error 400 when trying to use admin-ajax.php

These are the common reasons why would admin-ajax.php return a 400 error (or a HTTP status of "400 Bad Request") and then display a 0:

  1. Your AJAX action was never registered or it's registered late.

  2. Your JS script or AJAX request did not include the correct action value, i.e. the part after wp_ajax_ and wp_ajax_nopriv_.

  3. The current user is not logged-in and your AJAX action was registered only for logged-in users.

    Or the other way round — the user is logged-in, but your AJAX action was only for non logged-in users.

And in your case, where the AJAX action is getLVl2, the 400 error was likely due to reason #1 above, because your JS is sending the correct action and it's also registered for both logged-in and non logged-in users, i.e. you hooked on both wp_ajax_getLVl2 and wp_ajax_nopriv_getLVl2.

So you'd just need to ensure you are calling add_action() at the right time, e.g. in the main plugin file like so (which should be located at wp-content/plugins/my-plugin/my-plugin.php):

 * Plugin Name: My Plugin

function getLVl2() {
//  echo 'test';
    echo '<option>test</option>';
add_action( 'wp_ajax_getLVl2', 'getLVl2' );        // for logged-in users
add_action( 'wp_ajax_nopriv_getLVl2', 'getLVl2' ); // for non logged-in users

Now try, using your browser, visiting https://example.com/wp-admin/admin-ajax.php?action=getLVl2 and check if the test is displayed on the page.

  • 1
    Through some testing, I've already found out that the add_action and the functions called from the ajax should be in the main plugin file. Everything works now. Thanks for your help.
    – milenmk
    Commented Jun 21, 2022 at 6:44
  • Glad I could help :) And actually, they don't necessarily need to be in the main plugin file, so for example you can put them in external PHP file and load it via the main plugin file, e.g. require_once __DIR__ . '/path/to/your-file.php';. What's important for you to remember is that the add_action() should be called before or by the time WordPress runs the admin_init hook.
    – Sally CJ
    Commented Jun 21, 2022 at 11:45
  • I'm having the same problem as the OP in an admin-side app. My code is virtually the same both in the callback and the function, with the exception of ajaxurl for the url:. I've tried add_action( 'wp_ajax_some_function in both the calling .php and the called .php. So, my runtime error is https://foobar.com/wp-admin/admin-ajax.php 400 (Bad Request) and a response of 0. If I try it in the browser: https://foobar.com/wp-admin/admin-ajax.php?action=some_function, I get a 0. What else can I do to test?
    – breadwild
    Commented Dec 18, 2023 at 19:39
  • @breadwild I just wanted to say sorry for the late reply, and be sure to use the correct hook when registering your AJAX actions or calling add_action() 😉 (I've seen your question about the issue)
    – Sally CJ
    Commented Jan 15 at 10:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.