1

0

I'm working on a WordPress plugin and facing an issue with the Settings API. I have a settings page with two forms. The first form uses do_settings_sections() to call fields from a public function (callback). The second form renders the fields directly in the HTML.

When I submit the form that calls fields directly, it works fine. However, the form that uses do_settings_sections() and callbacks results in a "400 Bad Request" error when submitted.

Why isn't the do_settings_sections form submitting correctly?

Here's a simplified version of my code:

function snap_save_settings() {
    error_log('triggered snap_save_settings, Success!');
}
add_action('admin_post_snap_save_settings', 'snap_save_settings');

class Snap_Settings extends Snap_Settings_Page {
    protected function initialize_settings() {
        
        // General Settings Section
        $this->add_section('snap_main_settings', '', [$this, 'main_settings_section_callback'], 'snap-main-settings');
        $this->add_field('snap_main_settings', 'snap_linking_mode', '', [$this, 'linking_mode_callback'], 'snap-main-settings');   

    }
    
    //callback
    public function main_settings_section_callback() {
      
    }
    
    
    public function linking_mode_callback() {
        // Output a basic text input form field for testing
        echo '<label for="snap_test_input">Test Input:</label>';
        echo '<input type="text" id="snap_test_input" name="snap_test_input" value="">';
    }

    // Display Settings
    public function display_settings_page() {
        ?>
        <div class="wrap">
            <!-- Form using do_settings_sections() -->
            
            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
                <label>This form is rendered using do_settings_sections and does not work</label>
                <input type="hidden" name="action" value="snap_save_settings">
                <?php
                settings_fields('snap_settings_group');
                do_settings_sections('snap-main-settings');
                submit_button();
                ?>
            </form>
            <br>
            <br>
            <!-- Form with fields rendered directly -->
            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
                <label>This form is rendered directly and it works</label>
                <input type="hidden" name="action" value="snap_save_settings">
                <input type="text" name="snap_test_input" value="Test Value">
                <input type="submit" value="Save Changes">
            </form>
        </div>
        <?php
    }
}

new Snap_Settings();

Here is the settings base:

abstract class Snap_Settings_Page {
   private $sections = [];

   abstract protected function initialize_settings();

   public function __construct() {
       add_action('admin_menu', [$this, 'add_menu_page']);
       add_action('admin_init', [$this, 'register_settings']);
   }

   public function add_menu_page() {
       add_options_page(
           'Snap Settings', // Page title
           'Snap',          // Menu title
           'manage_options',     // Capability
           'snap-settings', // Menu slug
           [$this, 'display_settings_page'] // Callback function
       );
   }
   
   public function register_settings() {
       $this->initialize_settings();
   
       foreach ($this->sections as $section_id => $section) {
           add_settings_section(
               $section_id,
               $section['title'],
               $section['callback'],
               $section['page_slug']
           );
   
           foreach ($section['fields'] as $field_id => $field) {
               add_settings_field(
                   $field_id,
                   $field['title'],
                   $field['callback'],
                   $field['page_slug'],  // Use the page_slug from the field
                   $section_id
               );
               register_setting('snap_settings_group', $field_id);
           }
       }
   }

   protected function add_section($id, $title, $callback, $page_slug = 'snap-settings') {
       $this->sections[$id] = [
           'title' => $title,
           'callback' => $callback,
           'fields' => [],
           'page_slug' => $page_slug  // Store the page_slug with the section
       ];
   }

   protected function add_field($section_id, $field_id, $title, $callback, $page_slug = 'snap-settings') {
       if (!isset($this->sections[$section_id])) {
           // Optionally handle the error if the section does not exist
           return;
       }
   
       $this->sections[$section_id]['fields'][$field_id] = [
           'title' => $title,
           'callback' => $callback,
           'page_slug' => $page_slug
       ];
   }

   abstract public function display_settings_page();
   
}
3
  • I'm not sure about your problem but I do see that you register a setting after you've added it which makes no sense. It's the same as using a toaster before you buy it, or reading a book before it's written. I also see the code uses new Snap_Settings();, this means the object is never assigned to a variable and it's impossible to get a reference to it afterwards to unhook or do extra work, making debugging and extending this from other files super difficult
    – Tom J Nowell
    Commented Nov 20, 2023 at 11:59
  • Hi Tom, thanks, after moving the following register_setting up above the setting, the issue is not solved: register_setting('snap_settings_group', $field_id); You're right, I should've a variable assigned Snap_Settings() which I have now. How can I troubleshoot further? The callback inside the form stops the form action from triggering for some reason. Commented Nov 20, 2023 at 15:12
  • which callback specifically? Can you refer to it by name? And can you add the generated HTML? Edit these into your question instead of adding a comment so more people see it
    – Tom J Nowell
    Commented Nov 20, 2023 at 16:03

1 Answer 1

0

Thank you for the responses Tom! You got me on track, I should have checked the rendered html earlier. Here's the answer.

The rendered html was showing hidden inputs, for clarity, I'll just repeat my previous form with the hidden inputs that were being rendered:

<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
<label>This form wont work</label>
<input type="hidden" name="action" value="snap_save_settings">

//heres the hidden input that do_settings will always render
<input type="hidden" name="action" value="update">

                <?php
                settings_fields('snap_settings_group');
                do_settings_sections('snap-main-settings');
                submit_button();
                ?>
            </form>

The two issues here:

  1. The do_settings is already rendering an action that is hidden, I was adding additional action that wasnt being executed.
  2. do_settings will only execute the "update" action which wont work with admin-post.php, the update action is intended to be used with options.php.

I solved the problem by removing this line:

<input type="hidden" name="action" value="snap_save_settings">

Then posting to options.php rather than admin-post.php

If, like my case, you need to run a custom function (snap_save_settings), then you'll need to either hook into the updated-options hook or simply don't use do_settings.

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.