# Sequential Invoice Numbers for Orders

> Gives each paid order its own gap-free invoice number, such as INV-2026-0001, and shows it on the order screen, in emails and in the customer's order details.

- Type: PHP
- Runs: Run everywhere
- Category: [WooCommerce](https://wpide.com/library/category/woocommerce/)
- Requires: WooCommerce
- Tags: woocommerce, invoices, orders, accounting
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/woo-sequential-invoice-numbers/

## Code

```php
$wpide_invoice = array(
    'prefix' => 'INV-{year}-', // {year} is replaced with the current year.
    'start'  => 1,             // First number used.
    'digits' => 4,             // Zero-padded length.
);

$wpide_invoice_assign = function ( $order_id, $order = null ) use ( $wpide_invoice ) {
    global $wpdb;

    $order = $order instanceof WC_Order ? $order : wc_get_order( $order_id );

    if ( ! $order || $order->get_meta( '_wpide_invoice_number' ) ) {
        return;
    }

    add_option( 'wpide_invoice_counter', (string) ( (int) $wpide_invoice['start'] - 1 ), '', false );
    // Atomic increment: LAST_INSERT_ID(expr) returns the new value to this connection only.
    $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = LAST_INSERT_ID(option_value + 1) WHERE option_name = %s", 'wpide_invoice_counter' ) );
    $number = (int) $wpdb->get_var( 'SELECT LAST_INSERT_ID()' );
    wp_cache_delete( 'wpide_invoice_counter', 'options' );

    if ( $number < 1 ) {
        return;
    }

    $invoice = str_replace( '{year}', wp_date( 'Y' ), $wpide_invoice['prefix'] ) . str_pad( (string) $number, $wpide_invoice['digits'], '0', STR_PAD_LEFT );
    $order->update_meta_data( '_wpide_invoice_number', $invoice );
    $order->update_meta_data( '_wpide_invoice_date', time() );
    $order->save_meta_data();
};

add_action( 'woocommerce_order_status_processing', $wpide_invoice_assign, 5, 2 );
add_action( 'woocommerce_order_status_completed', $wpide_invoice_assign, 5, 2 );

add_action( 'woocommerce_admin_order_data_after_order_details', function ( $order ) {
    $invoice = $order->get_meta( '_wpide_invoice_number' );

    if ( $invoice ) {
        printf( '<p class="form-field form-field-wide"><strong>Invoice:</strong> %s</p>', esc_html( $invoice ) );
    }
} );

add_filter( 'woocommerce_email_order_meta_fields', function ( $fields, $sent_to_admin, $order ) {
    $invoice = $order->get_meta( '_wpide_invoice_number' );

    if ( $invoice ) {
        $fields['wpide_invoice'] = array( 'label' => 'Invoice number', 'value' => $invoice );
    }

    return $fields;
}, 10, 3 );

add_filter( 'woocommerce_get_order_item_totals', function ( $rows, $order ) {
    $invoice = $order->get_meta( '_wpide_invoice_number' );

    if ( $invoice ) {
        $rows = array( 'wpide_invoice' => array( 'label' => 'Invoice number:', 'value' => esc_html( $invoice ) ) ) + $rows;
    }

    return $rows;
}, 10, 2 );
```

## Notes

Numbers are assigned when an order reaches Processing or Completed, never change, and use an atomic counter so two orders cannot share one.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Sequential Invoice Numbers for Orders" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
