Create new order state and status programmatically in Magento 2

Every order has a status associated with a stage in the order processing workflow.
The state describes the position of an order in the workflow. By default, Magento store has a set of predefined order statuses and order state settings. All the available order statuses can be found in admin panel under Stores -> Settings -> Order Status. Sometimes we need to create a new order status and state. Let’s explore how to programmatically create a new order state and status in Magento 2.

The order statuses are stored in sales_order_status database table while the order states and their bindings to statuses are defined in sales_order_status_state table. We will need to create a simple setup script in order to add a new order state and status.

First of all we need to create a new custom extension, for example Atwix_OrderFlow. Create a registration.php file in the app/code/Atwix/OrderFlow:

<?php
/* File: app/code/Atwix/OrderFlow/registration.php */

use \Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Atwix_OrderFlow',
    __DIR__
);

And create the module.xml configuration file in app/code/Atwix/OrderFlow/etc folder with the following code:

<?xml version="1.0"?>
<!-- File: app/code/Atwix/OrderFlow/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Atwix_OrderFlow" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Sales"/>
        </sequence>
    </module>
</config>

The installation data class is as follows. For instance, two similar methods have been created:

  • addNewOrderProcessingStatus
  • addNewOrderStateAndStatus

The first method will add a new order status to the existent order state. The second one will create a new order status and new order state.

<?php
/* File: app/code/Atwix/OrderFlow/Setup/InstallData.php */

namespace Atwix\OrderFlow\Setup;

use Exception;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Status;
use Magento\Sales\Model\Order\StatusFactory;
use Magento\Sales\Model\ResourceModel\Order\Status as StatusResource;
use Magento\Sales\Model\ResourceModel\Order\StatusFactory as StatusResourceFactory;

/**
 * Class InstallData
 */
class InstallData implements InstallDataInterface
{
    /**
     * Custom Processing Order-Status code
     */
    const ORDER_STATUS_PROCESSING_FULFILLMENT_CODE = 'processing_custom';

    /**
     * Custom Processing Order-Status label
     */
    const ORDER_STATUS_PROCESSING_FULFILLMENT_LABEL = 'Processing Custom';

    /**
     * Custom Order-State code
     */
    const ORDER_STATE_CUSTOM_CODE = 'some_custom_state';

    /**
     * Custom Order-Status code
     */
    const ORDER_STATUS_CUSTOM_CODE = 'some_custom_status';

    /**
     * Custom Order-Status label
     */
    const ORDER_STATUS_CUSTOM_LABEL = 'Some Custom Status';

    /**
     * Status Factory
     *
     * @var StatusFactory
     */
    protected $statusFactory;

    /**
     * Status Resource Factory
     *
     * @var StatusResourceFactory
     */
    protected $statusResourceFactory;

    /**
     * InstallData constructor
     *
     * @param StatusFactory $statusFactory
     * @param StatusResourceFactory $statusResourceFactory
     */
    public function __construct(
        StatusFactory $statusFactory,
        StatusResourceFactory $statusResourceFactory
    ) {
        $this->statusFactory = $statusFactory;
        $this->statusResourceFactory = $statusResourceFactory;
    }

    /**
     * Installs data for a module
     *
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     *
     * @return void
     *
     * @throws Exception
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $this->addNewOrderProcessingStatus();
        $this->addNewOrderStateAndStatus();
    }

    /**
     * Create new order processing status and assign it to the existent state
     *
     * @return void
     *
     * @throws Exception
     */
    protected function addNewOrderProcessingStatus()
    {
        /** @var StatusResource $statusResource */
        $statusResource = $this->statusResourceFactory->create();
        /** @var Status $status */
        $status = $this->statusFactory->create();
        $status->setData([
            'status' => self::ORDER_STATUS_PROCESSING_FULFILLMENT_CODE,
            'label' => self::ORDER_STATUS_PROCESSING_FULFILLMENT_LABEL,
        ]);

        try {
            $statusResource->save($status);
        } catch (AlreadyExistsException $exception) {

            return;
        }

        $status->assignState(Order::STATE_PROCESSING, false, true);
    }

    /**
     * Create new custom order status and assign it to the new custom order state
     *
     * @return void
     *
     * @throws Exception
     */
    protected function addNewOrderStateAndStatus()
    {
        /** @var StatusResource $statusResource */
        $statusResource = $this->statusResourceFactory->create();
        /** @var Status $status */
        $status = $this->statusFactory->create();
        $status->setData([
            'status' => self::ORDER_STATUS_CUSTOM_CODE,
            'label' => self::ORDER_STATUS_CUSTOM_LABEL,
        ]);

        try {
            $statusResource->save($status);
        } catch (AlreadyExistsException $exception) {

            return;
        }

        $status->assignState(self::ORDER_STATE_CUSTOM_CODE, true, true);
    }
}

As you may have noticed, each method performs two steps. First of all, it creates a new order status and saves it, so it will appear in sales_order_status database table. Then it links the created status to an order state by adding a new record to the sales_order_status_state database table.

There are no separate table for order states, so each unique value in state column of sales_order_status_state table is considered as a separate order state.

Run the Setup Upgrade command in order to activate the module and execute the setup script:

php bin/magento setup:upgrade

Now you can check the results in Stores -> Settings -> Order Status.

How to create new order state and status programmatically in Magento 2

Thanks for reading!