WooCommerce Code Snippet

Add Custom Order Status Dropdown in WooCommerce Admin

Learn how to add an internal custom order dropdown to WooCommerce orders, so you can save and display an additional status such as Dispatched or Not dispatched.

WooCommerce Admin Orders PHP Snippet
Add Custom Order Status Dropdown in WooCommerce Admin

What this snippet does

By default, WooCommerce includes core order statuses such as Pending, Processing, Completed and Cancelled. In many stores, however, you may need an extra internal indicator without creating a full custom WooCommerce order status.

This snippet adds a custom dropdown inside the order edit screen, saves the selected value as order meta and displays it in the WooCommerce orders list.

Internal status Track intermediate stages such as dispatch, packing or courier waiting.
Better admin workflow Your team can view the information directly from the orders list.
No core status change The default WooCommerce order status flow remains untouched.

1) Why add a custom order dropdown?

An internal order status dropdown is useful when you want to store additional information for each order without mixing it with the default WooCommerce order statuses.

  • Track internal states such as “Dispatched”, “Packed” or “Awaiting Courier”.
  • Improve order management inside WooCommerce Admin.
  • Give staff a clear status update without changing the main order status flow.
  • Display a quick visual indicator directly inside the WooCommerce orders list.
This approach is ideal when the information is mainly for internal use and does not need to be shown to the customer.

2) Before adding the code

Before using any snippet that modifies the WooCommerce admin area, make sure you follow a basic safety checklist.

  • Take a full backup of your files and database.
  • Test the snippet on a staging environment first.
  • Use a child theme or a small custom plugin, not the parent theme directly.
  • Confirm that admin users can save orders correctly after adding the snippet.
  • If you use HPOS, test the WooCommerce orders list after installation.

3) Code Snippet

Add the following code to your child theme’s functions.php file or, preferably, inside a small custom plugin.

woocommerce-custom-order-status-dropdown.php
/**
 * Add a custom internal order status dropdown in WooCommerce admin.
 * Stores the value as order meta and displays it in the orders list.
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Available internal status options.
 */
function vgdev_internal_order_status_options() {
    return array(
        'not_dispatched'  => __( 'Not dispatched', 'woocommerce' ),
        'dispatched'      => __( 'Dispatched', 'woocommerce' ),
        'packed'          => __( 'Packed', 'woocommerce' ),
        'awaiting_courier' => __( 'Awaiting courier', 'woocommerce' ),
    );
}

/**
 * Get saved internal status for an order.
 */
function vgdev_get_internal_order_status( $order ) {
    if ( ! $order instanceof WC_Order ) {
        return 'not_dispatched';
    }

    $status  = $order->get_meta( '_vgdev_internal_order_status', true );
    $options = vgdev_internal_order_status_options();

    return array_key_exists( $status, $options ) ? $status : 'not_dispatched';
}

/**
 * Add dropdown inside the order edit screen.
 */
add_action( 'woocommerce_admin_order_data_after_order_details', 'vgdev_add_internal_order_status_dropdown' );

function vgdev_add_internal_order_status_dropdown( $order ) {
    if ( ! $order instanceof WC_Order ) {
        return;
    }

    $saved_status = vgdev_get_internal_order_status( $order );
    $options      = vgdev_internal_order_status_options();

    wp_nonce_field( 'vgdev_save_internal_order_status', 'vgdev_internal_order_status_nonce' );
    ?>

    <p class="form-field form-field-wide">
        <label for="vgdev_internal_order_status">
            <?php esc_html_e( 'Internal order status', 'woocommerce' ); ?>
        </label>

        <select id="vgdev_internal_order_status" name="vgdev_internal_order_status">
            <?php foreach ( $options as $value => $label ) : ?>
                <option value="<?php echo esc_attr( $value ); ?>" <?php selected( $saved_status, $value ); ?>>
                    <?php echo esc_html( $label ); ?>
                </option>
            <?php endforeach; ?>
        </select>

        <span class="description">
            <?php esc_html_e( 'Used internally inside WooCommerce admin only.', 'woocommerce' ); ?>
        </span>
    </p>

    <?php
}

/**
 * Save dropdown value when the order is saved.
 */
add_action( 'woocommerce_process_shop_order_meta', 'vgdev_save_internal_order_status_dropdown', 20, 1 );

function vgdev_save_internal_order_status_dropdown( $order_id ) {
    if (
        ! isset( $_POST['vgdev_internal_order_status_nonce'] ) ||
        ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['vgdev_internal_order_status_nonce'] ) ), 'vgdev_save_internal_order_status' )
    ) {
        return;
    }

    if ( ! current_user_can( 'edit_shop_order', $order_id ) ) {
        return;
    }

    $order = wc_get_order( $order_id );

    if ( ! $order ) {
        return;
    }

    $options = vgdev_internal_order_status_options();
    $status  = isset( $_POST['vgdev_internal_order_status'] )
        ? sanitize_key( wp_unslash( $_POST['vgdev_internal_order_status'] ) )
        : 'not_dispatched';

    if ( ! array_key_exists( $status, $options ) ) {
        $status = 'not_dispatched';
    }

    $order->update_meta_data( '_vgdev_internal_order_status', $status );
    $order->save();
}

/**
 * Add column to legacy WooCommerce orders list.
 */
add_filter( 'manage_edit-shop_order_columns', 'vgdev_add_internal_order_status_column', 20 );

/**
 * Add column to HPOS WooCommerce orders list.
 */
add_filter( 'manage_woocommerce_page_wc-orders_columns', 'vgdev_add_internal_order_status_column', 20 );

function vgdev_add_internal_order_status_column( $columns ) {
    $new_columns = array();

    foreach ( $columns as $key => $column ) {
        $new_columns[ $key ] = $column;

        if ( 'order_status' === $key ) {
            $new_columns['vgdev_internal_order_status'] = __( 'Internal status', 'woocommerce' );
        }
    }

    return $new_columns;
}

/**
 * Render column for legacy orders list.
 */
add_action( 'manage_shop_order_posts_custom_column', 'vgdev_render_internal_order_status_column_legacy', 20, 2 );

function vgdev_render_internal_order_status_column_legacy( $column, $post_id ) {
    if ( 'vgdev_internal_order_status' !== $column ) {
        return;
    }

    $order = wc_get_order( $post_id );

    vgdev_render_internal_order_status_badge( $order );
}

/**
 * Render column for HPOS orders list.
 */
add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'vgdev_render_internal_order_status_column_hpos', 20, 2 );

function vgdev_render_internal_order_status_column_hpos( $column, $order ) {
    if ( 'vgdev_internal_order_status' !== $column ) {
        return;
    }

    vgdev_render_internal_order_status_badge( $order );
}

/**
 * Output admin badge.
 */
function vgdev_render_internal_order_status_badge( $order ) {
    if ( ! $order instanceof WC_Order ) {
        echo '&mdash;';
        return;
    }

    $status  = vgdev_get_internal_order_status( $order );
    $options = vgdev_internal_order_status_options();
    $label   = isset( $options[ $status ] ) ? $options[ $status ] : $options['not_dispatched'];

    echo '<mark class="order-status status-' . esc_attr( $status ) . '"><span>' . esc_html( $label ) . '</span></mark>';
}
The snippet uses WooCommerce CRUD methods for saving order meta and includes hooks for both the legacy orders list and the HPOS orders list.

4) How it works

It defines internal status options The vgdev_internal_order_status_options() function defines the available dropdown options.
It displays a dropdown inside the order The woocommerce_admin_order_data_after_order_details hook adds the field to the order edit screen.
It saves the value as order meta The selected value is stored in the meta key _vgdev_internal_order_status.
It adds a new column to the orders list The column displays the selected internal status in the WooCommerce orders list.
It supports legacy and HPOS order screens The snippet includes hooks for both shop_order and wc-orders.

5) How to change the dropdown options

The available options are defined inside vgdev_internal_order_status_options(). You can add, remove or rename statuses based on your store workflow.

customize-options.php
function vgdev_internal_order_status_options() {
    return array(
        'not_dispatched'  => __( 'Not dispatched', 'woocommerce' ),
        'dispatched'      => __( 'Dispatched', 'woocommerce' ),
        'packed'          => __( 'Packed', 'woocommerce' ),
        'awaiting_courier' => __( 'Awaiting courier', 'woocommerce' ),
    );
}
Not dispatched Dispatched Packed Awaiting courier

6) Where to add the code

There are three common ways to add this snippet to your WooCommerce site.

  • Child theme functions.php: a quick solution, as long as you are using a child theme.
  • Custom plugin: the cleaner and safer option, because the functionality does not depend on the active theme.
  • Snippet plugin: practical if you already manage snippets from the WordPress admin.
Do not add this code to the parent theme, because your changes may be lost after a theme update.

7) Testing after installation

After adding the snippet, run a quick test to confirm that everything works correctly.

  • Open an existing WooCommerce order.
  • Find the Internal order status field.
  • Select a value, for example Dispatched.
  • Save or update the order.
  • Return to the WooCommerce orders list.
  • Confirm that the new column displays the correct value.

8) Best practices

  • Use simple labels that your team can understand immediately.
  • Avoid adding too many dropdown options.
  • Keep this field for internal order management, not customer-facing updates.
  • Test the snippet on staging before adding it to the live site.
  • On multilingual sites, translate the labels for each language.
  • If you use HPOS, test the column inside the new WooCommerce orders list.

9) Frequently Asked Questions

Does this change the real WooCommerce order status?

No. The snippet saves a separate order meta value and does not change the core WooCommerce order status.

Will customers see this value?

No by default. The information is displayed only inside WooCommerce admin.

Can I change the dropdown options?

Yes. Edit the options inside the vgdev_internal_order_status_options() function.

Is it compatible with HPOS?

The snippet uses WooCommerce order meta methods and includes hooks for the new HPOS orders list. Still, you should always test it on your own setup.

Where is the value stored?

The value is stored in the order meta key _vgdev_internal_order_status.

10) Support

For any issue or question related to WooCommerce snippets, you can contact technical support.

info@vgdevsolutions.gr

Related posts