In this tutorial, we will learn to use the add_meta_box function to create a settings form for plugins and themes with collapsible meta boxes using postbox.js. By the end of this tutorial, you’ll be able to easily implement a similar settings form, as shown in the following image.

Form Created Using the add_meta_box Function
Form Created Using the add_meta_box Function

In this tutorial, we will cover the following points:

  1. Enabling or disabling specific columns using screen options.
  2. Adjusting the column layout using screen options.
  3. Implementing collapsible meta boxes.
  4. Storing and accessing form data with the update_option and get_option functions.

Adding Code to the functions.php File

For simplicity, we’ll use the functions.php file to create forms for managing SMTP settings and sending emails. To prevent losing changes during theme updates, it’s essential to create a child theme. Once the child theme is set up, you can insert the code into the functions.php file of the child theme.

Creating an Admin Page

To display our forms in the WordPress Admin panel, we’ll create a custom admin page using the admin_menu function. Add the following code to your functions.php file to create a settings page for your plugin or theme.

function init_mail_settings() {
    echo "SMTP Settings";
}

function smtp_menu() {
    global $smtp_menu_page;
    $smtp_menu_page = add_menu_page(__('SMTP', 'theme-name'), __('SMTP', 'theme-name'), 'manage_options', 'setup-smtp', 'init_mail_settings', 'dashicons-email', 110);
    // add_menu_page( string $page_title, string $menu_title, string $capability, string $menu_slug, callable $callback = '', string $icon_url = '', int|float $position = null )
}

add_action('admin_menu', 'smtp_menu');

Display Registered Meta Boxes

After creating the custom admin page, we need to render all meta boxes registered to this page. To do this, we’ll call the do_meta_boxes function within the init_mail_settings function. Update the init_mail_settings function with the following code.

function init_mail_settings() {
    $screen = get_current_screen(); ?>
    <div class="wrap">
        <h1 class="wp-heading-inline"><?php _e('SMTP Settings', 'theme-name'); ?></h1>
        <div>
            <div id="postbox-container-1" class="postbox-container"><?php do_meta_boxes( $screen->id, 'normal', '' ); ?></div>
            <div id="postbox-container-2" class="postbox-container"><?php do_meta_boxes( $screen->id, 'side', '' ); ?></div>
        </div>
    </div>
<?php }

Register Meta Boxes

Next, we need to register our meta boxes using the add_meta_box function. Add the following code to your functions.php file to register all custom meta boxes.

function smtp_configuration_form() {
    echo "SMTP Configuration Form";
}

function wp_mail_form() {
    echo "WP Mail Form";
}

function smtp_mail_page_metaboxes() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }
    add_meta_box('smtp-configuration', 'SMTP Configuration', 'smtp_configuration_form', $smtp_menu_page, 'normal', 'default');
    add_meta_box('mail-system', 'Mail', 'wp_mail_form', $smtp_menu_page, 'side', 'default');
    // add_meta_box( string $id, string $title, callable $callback, string|array|WP_Screen $screen = null, string $context = ‘advanced’, string $priority = ‘default’, array $callback_args = null )
}

add_action('add_meta_boxes_smtp_mail_page','smtp_mail_page_metaboxes');

After refreshing the custom admin page, you might notice that the meta boxes still aren’t rendering. This happens because we haven’t yet created the action hook add_meta_boxes_smtp_mail_page, which executes the code to register the meta boxes.

Adding Support for Screen Options

To fix this, we’ll create an action hook called add_meta_boxes_smtp_mail_page using the do_action function in WordPress. This hook will trigger the code to register the meta boxes.

function smtp_mail_screen_options() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }

    do_action('add_meta_boxes_smtp_mail_page', $smtp_menu_page);
}

Now, update the smtp_menu function (from the “Create Admin Page” step) with the following code to enable support for screen options.

function smtp_menu() {
    global $smtp_menu_page;
    $smtp_menu_page = add_menu_page(__('SMTP', 'theme-name'), __('SMTP', 'theme-name'), 'manage_options', 'setup-smtp', 'init_mail_settings', 'dashicons-email', 110);
    add_action("load-{$smtp_menu_page}", "smtp_mail_screen_options");
}

Once you refresh the page, you should see two meta boxes: one for the SMTP configuration form and another for the WordPress mail service.

Enable Support for Collapsible Meta Boxes

If you click on the collapsible arrow in your custom meta boxes, you may notice that they don’t collapse. To fix this, we need to enqueue the postbox library by using the wp_enqueue_script function and call the add_postbox_toggles function from the postbox library. Update the smtp_mail_screen_options and smtp_menu functions with the following code to resolve this issue.

function smtp_mail_screen_options() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }

    do_action('add_meta_boxes_smtp_mail_page', $smtp_menu_page);

    /* Enqueue postbox script for handling the metaboxes */
    wp_enqueue_script('postbox');
}

function smtp_mail_footer_scripts() {
    echo '<script>jQuery(document).ready(function(){if(postboxes){postboxes.add_postbox_toggles(pagenow);}});</script>';
}

function smtp_menu() {
    global $smtp_menu_page;
    $smtp_menu_page = add_menu_page(__('SMTP', 'theme-name'), __('SMTP', 'theme-name'), 'manage_options', 'setup-smtp', 'init_mail_settings', 'dashicons-email', 110);
    add_action("load-{$smtp_menu_page}", "smtp_mail_screen_options");
    add_action("admin_footer-{$smtp_menu_page}", "smtp_mail_footer_scripts");
}

Add Screen Option to Change Column Layout

If you check the “Screen Options” tab, you might see that the option to toggle the column layout is missing. To enable this feature, use the add_screen_option function. Update the smtp_mail_screen_options function with the following code to add the column layout option.

function smtp_mail_screen_options() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }

    do_action('add_meta_boxes_smtp_mail_page', $smtp_menu_page);

    /* User can choose between 1 or 2 columns (default 2) */
    add_screen_option('layout_columns', array('max' => 2, 'default' => 1) );

    /* Enqueue postbox script for handling the metaboxes */
    wp_enqueue_script('postbox');
}

Fix Column Layout Setting

When you toggle the column layout setting, you may find that it isn’t working. To fix this, you need to add the dashboard-widgets ID and metabox-holder$columns_css class to the parent <div> tag of the postbox container. Update the init_mail_settings function with the following code to fix this issue.

function init_mail_settings() {
    $screen = get_current_screen();
    $columns = absint( $screen->get_columns() );
    $columns_css = '';
    if ( $columns ) {
        $columns_css = " columns-$columns";
    } ?>
    <div class="wrap">
        <h1 class="wp-heading-inline"><?php _e('SMTP Settings', 'theme-name'); ?></h1>
        <div id="dashboard-widgets-wrap">
            <div id="dashboard-widgets" class="metabox-holder<?php echo $columns_css; ?>">
                <div id="postbox-container-1" class="postbox-container"><?php do_meta_boxes( $screen->id, 'normal', '' ); ?></div>
                <div id="postbox-container-2" class="postbox-container"><?php do_meta_boxes( $screen->id, 'side', '' ); ?></div>
            </div>
        </div>
    </div>
<?php }

Create Forms Using HTML

To create forms for managing SMTP (Simple Mail Transfer Protocol) settings and WordPress mail sending functionality, update the smtp_configuration_form and wp_mail_form functions (defined in the “Register Meta Boxes” step) with the following code.

function smtp_configuration_form() {
    if(isset($_POST["submit"])) {
        $smtp_config = [
            'host' => $_POST['host'],
            'port' => $_POST['port'],
            'username' => $_POST['username'],
            'password' => $_POST['password'],
            'security_type' => $_POST['security-type'],
        ];
        update_option( "smtp_config", $smtp_config );
    }
    $smtp_config = get_option( "smtp_config" ); ?>
    <form id="smtp-conf" action="#" method="post">
        <div class="textarea-wrap">
            <label for="host" style="display: block"><?php _e('SMTP Host', 'theme-name'); ?></label>
            <input type="text" name="host" id="host" placeholder="<?php _e('smtp.example.com', 'theme-name'); ?>" value="<?php echo isset($smtp_config['host']) ? $smtp_config['host'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="port" style="display: block"><?php _e('SMTP Port', 'theme-name'); ?></label>
            <input type="number" name="port" id="port" placeholder="<?php _e('25, 465 or 587', 'theme-name'); ?>" value="<?php echo isset($smtp_config['port']) ? $smtp_config['port'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="username" style="display: block"><?php _e('SMTP Username', 'theme-name'); ?></label>
            <input type="text" name="username" id="username" value="<?php echo isset($smtp_config['username']) ? $smtp_config['username'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="password" style="display: block"><?php _e('SMTP Password', 'theme-name'); ?></label>
            <input type="password" name="password" id="password" value="<?php echo isset($smtp_config['password']) ? $smtp_config['password'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="security-type" style="display: block"><?php _e('SMTP Security', 'theme-name'); ?></label>
            <select name="security-type" id="security-type">
                <option><?php _e('None', 'theme-name'); ?></option>
                <option value="tls" <?php echo isset($smtp_config['security_type']) && $smtp_config['security_type'] === 'tls' ? 'selected' : ''; ?>><?php _e('TLS', 'theme-name'); ?></option>
                <option value="ssl" <?php echo isset($smtp_config['security_type']) && $smtp_config['security_type'] === 'ssl' ? 'selected' : ''; ?>><?php _e('SSL', 'theme-name'); ?></option>
            </select>
        </div>
        <p class="submit" style="margin-top: 1rem">
            <input type="submit" name="submit" id="save-conf" class="button button-primary" value="<?php _e('Submit', 'theme-name'); ?>" />
            <br class="clear" />
        </p>
    </form>
<?php }

function wp_mail_form() {
    if(isset($_POST["send"])) {
        $email = $_POST['sender-email'];
        $cc = $_POST['cc-email'];
        $bcc = $_POST['bcc-email'];
        wp_mail( $_POST['receiver-email'], $_POST['email-subject'], $_POST['message'], array( "From: {$email}", "Cc: {$cc}", "Bcc: {$bcc}" ) );
    } ?>
    <form id="wp-mail-sender" action="#" method="post">
        <div class="textarea-wrap">
            <label for="sender-email" style="display: block"><?php _e('From', 'theme-name'); ?></label>
            <input type="text" name="sender-email" id="sender-email" />
        </div>
        <div class="textarea-wrap">
            <label for="receiver-email" style="display: block"><?php _e('To', 'theme-name'); ?></label>
            <input type="text" name="receiver-email" id="receiver-email" />
        </div>
        <div class="textarea-wrap">
            <label for="cc-email" style="display: block"><?php _e('Cc', 'theme-name'); ?></label>
            <input type="text" name="cc-email" id="cc-email" />
        </div>
        <div class="textarea-wrap">
            <label for="bcc-email" style="display: block"><?php _e('Bcc', 'theme-name'); ?></label>
            <input type="text" name="bcc-email" id="bcc-email" />
        </div>
        <div class="textarea-wrap">
            <label for="email-subject" style="display: block"><?php _e('Subject', 'theme-name'); ?></label>
            <input type="text" name="email-subject" id="email-subject" />
        </div>
        <div class="textarea-wrap">
            <label for="message" style="display: block"><?php _e("Message", 'theme-name'); ?></label>
            <textarea name="message" id="message" class="mceEditor" rows="10" cols="15"></textarea>
        </div>
        <p class="submit" style="margin-top: 1rem">
            <input type="submit" name="send-mail" id="send-mail" class="button button-primary" value="<?php _e('Send', 'theme-name'); ?>" />
            <br class="clear" />
        </p>
    </form>
<?php }

In the SMTP settings form, we use the update_option and get_option functions to store and retrieve form data. This data can be used to configure your SMTP server. For more information on setting up an SMTP server, refer to our article on How to Set Up SMTP in WordPress.

Complete Code to Create Forms Using Meta Boxes

Here’s the complete code for creating forms on custom admin pages using WordPress Meta Boxes:

<?php

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

function init_mail_settings() {
    $screen = get_current_screen();
    $columns = absint( $screen->get_columns() );
    $columns_css = '';
    if ( $columns ) {
        $columns_css = " columns-$columns";
    } ?>
    <div class="wrap">
        <h1 class="wp-heading-inline"><?php _e('SMTP Settings', 'theme-name'); ?></h1>
        <div id="dashboard-widgets-wrap">
            <div id="dashboard-widgets" class="metabox-holder<?php echo $columns_css; ?>">
                <div id="postbox-container-1" class="postbox-container"><?php do_meta_boxes( $screen->id, 'normal', '' ); ?></div>
                <div id="postbox-container-2" class="postbox-container"><?php do_meta_boxes( $screen->id, 'side', '' ); ?></div>
            </div>
        </div>
    </div>
<?php }

function smtp_menu() {
    global $smtp_menu_page;
    $smtp_menu_page = add_menu_page(__('SMTP', 'theme-name'), __('SMTP', 'theme-name'), 'manage_options', 'setup-smtp', 'init_mail_settings', 'dashicons-email', 110);
    add_action("load-{$smtp_menu_page}", "smtp_mail_screen_options");
    add_action("admin_footer-{$smtp_menu_page}", "smtp_mail_footer_scripts");
}

add_action('admin_menu', 'smtp_menu');

function smtp_configuration_form() {
    if(isset($_POST["submit"])) {
        $smtp_config = [
            'host' => $_POST['host'],
            'port' => $_POST['port'],
            'username' => $_POST['username'],
            'password' => $_POST['password'],
            'security_type' => $_POST['security-type'],
        ];
        update_option( "smtp_config", $smtp_config );
    }
    $smtp_config = get_option( "smtp_config" ); ?>
    <form id="smtp-conf" action="#" method="post">
        <div class="textarea-wrap">
            <label for="host" style="display: block"><?php _e('SMTP Host', 'theme-name'); ?></label>
            <input type="text" name="host" id="host" placeholder="<?php _e('smtp.example.com', 'theme-name'); ?>" value="<?php echo isset($smtp_config['host']) ? $smtp_config['host'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="port" style="display: block"><?php _e('SMTP Port', 'theme-name'); ?></label>
            <input type="number" name="port" id="port" placeholder="<?php _e('25, 465 or 587', 'theme-name'); ?>" value="<?php echo isset($smtp_config['port']) ? $smtp_config['port'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="username" style="display: block"><?php _e('SMTP Username', 'theme-name'); ?></label>
            <input type="text" name="username" id="username" value="<?php echo isset($smtp_config['username']) ? $smtp_config['username'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="password" style="display: block"><?php _e('SMTP Password', 'theme-name'); ?></label>
            <input type="password" name="password" id="password" value="<?php echo isset($smtp_config['password']) ? $smtp_config['password'] : '' ?>" />
        </div>
        <div class="textarea-wrap">
            <label for="security-type" style="display: block"><?php _e('SMTP Security', 'theme-name'); ?></label>
            <select name="security-type" id="security-type">
                <option><?php _e('None', 'theme-name'); ?></option>
                <option value="tls" <?php echo isset($smtp_config['security_type']) && $smtp_config['security_type'] === 'tls' ? 'selected' : ''; ?>><?php _e('TLS', 'theme-name'); ?></option>
                <option value="ssl" <?php echo isset($smtp_config['security_type']) && $smtp_config['security_type'] === 'ssl' ? 'selected' : ''; ?>><?php _e('SSL', 'theme-name'); ?></option>
            </select>
        </div>
        <p class="submit" style="margin-top: 1rem">
            <input type="submit" name="submit" id="save-conf" class="button button-primary" value="<?php _e('Submit', 'theme-name'); ?>" />
            <br class="clear" />
        </p>
    </form>
<?php }

function wp_mail_form() {
    if(isset($_POST["send"])) {
        $email = $_POST['sender-email'];
        $cc = $_POST['cc-email'];
        $bcc = $_POST['bcc-email'];
        wp_mail( $_POST['receiver-email'], $_POST['email-subject'], $_POST['message'], array( "From: {$email}", "Cc: {$cc}", "Bcc: {$bcc}" ) );
    } ?>
    <form id="wp-mail-sender" action="#" method="post">
        <div class="textarea-wrap">
            <label for="sender-email" style="display: block"><?php _e('From', 'theme-name'); ?></label>
            <input type="text" name="sender-email" id="sender-email" />
        </div>
        <div class="textarea-wrap">
            <label for="receiver-email" style="display: block"><?php _e('To', 'theme-name'); ?></label>
            <input type="text" name="receiver-email" id="receiver-email" />
        </div>
        <div class="textarea-wrap">
            <label for="cc-email" style="display: block"><?php _e('Cc', 'theme-name'); ?></label>
            <input type="text" name="cc-email" id="cc-email" />
        </div>
        <div class="textarea-wrap">
            <label for="bcc-email" style="display: block"><?php _e('Bcc', 'theme-name'); ?></label>
            <input type="text" name="bcc-email" id="bcc-email" />
        </div>
        <div class="textarea-wrap">
            <label for="email-subject" style="display: block"><?php _e('Subject', 'theme-name'); ?></label>
            <input type="text" name="email-subject" id="email-subject" />
        </div>
        <div class="textarea-wrap">
            <label for="message" style="display: block"><?php _e("Message", 'theme-name'); ?></label>
            <textarea name="message" id="message" class="mceEditor" rows="10" cols="15"></textarea>
        </div>
        <p class="submit" style="margin-top: 1rem">
            <input type="submit" name="send-mail" id="send-mail" class="button button-primary" value="<?php _e('Send', 'theme-name'); ?>" />
            <br class="clear" />
        </p>
    </form>
<?php }

function smtp_mail_page_metaboxes() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }
    add_meta_box('smtp-configuration', 'SMTP Configuration', 'smtp_configuration_form', $smtp_menu_page, 'normal', 'default');
    add_meta_box('mail-system', 'Mail', 'wp_mail_form', $smtp_menu_page, 'side', 'default');
    // add_meta_box( string $id, string $title, callable $callback, string|array|WP_Screen $screen = null, string $context = ‘advanced’, string $priority = ‘default’, array $callback_args = null )
}

add_action('add_meta_boxes_smtp_mail_page','smtp_mail_page_metaboxes');

function smtp_mail_screen_options() {
    global $smtp_menu_page;
    $screen = get_current_screen();
    if(!is_object($screen) || $screen->id !== $smtp_menu_page) {
        return;
    }

    do_action('add_meta_boxes_smtp_mail_page', $smtp_menu_page);

    /* User can choose between 1 or 2 columns (default 2) */
    add_screen_option('layout_columns', array('max' => 2, 'default' => 1) );

    /* Enqueue postbox script for handling the metaboxes */
    wp_enqueue_script('postbox');
}

function smtp_mail_footer_scripts() {
    echo '<script>jQuery(document).ready(function(){if(postboxes){postboxes.add_postbox_toggles(pagenow);}});</script>';
}