Managing order statuses is critical to running a successful Magento 2 store — and sooner or later you'll need to change a status programmatically rather than manually: syncing with a fulfillment system, reacting to a payment webhook, or bulk-updating orders on a condition.
This blog walks you through exactly that, including the one mistake that quietly corrupts order workflows (mixing up state and status). MageAnts is a certified Magento 2 development company and we've helped many merchants build this kind of automation into their stores — here's how it's done properly.
Let's begin!
State vs. Status: Get This Right First
Magento orders carry two related fields, and confusing them is the most common bug in status-change code:
- Order State — the system's internal workflow stage: new, processing, complete, closed, canceled, holded. Fixed values that Magento's logic (invoicing, shipping, refunds) depends on.
- Order Status — the human-readable label shown in the admin and to customers, like Pending Payment or your own Ready for Pickup. Every status is assigned to a state, and one state can hold many statuses.
The rule: change the status freely; only change the state when the order genuinely moves to another workflow stage — and never set a custom status code as the state. Doing that puts the order in a state Magento doesn't recognize, and things like the Invoice and Ship buttons can silently break.
Steps to Change Order Status Programmatically in Magento 2
Step 1: Load the Order
Load the order by ID through OrderRepositoryInterface (injected via your class constructor — full example below):
$orderId = 123; // Replace with the actual order ID $order = $this->orderRepository->get($orderId); |
Step 2: Set the New Status
Set the status — and leave the state alone unless the order is moving to a new workflow stage:
$newStatus = 'ready_for_pickup'; // Your status code $order->setStatus($newStatus); // Only when the workflow stage actually changes, set the state too — // always with a state constant, never a custom status code: // $order->setState(\Magento\Sales\Model\Order::STATE_PROCESSING) // ->setStatus($newStatus); |
Note: the status code must already exist and be assigned to the order's state in your Magento backend (Stores > Settings > Order Status) — assigning a status from a different state won't display correctly.
Step 3: Save the Order
$this->orderRepository->save($order); |
And that's it — the order's status is updated programmatically.
Complete Example: Magento 2 Change Order Status Programmatically
Here's the whole thing as a proper class with dependency injection — usable as-is in a controller, cron job, observer, or console command. It also adds a status history comment, which is what you'll almost always want in practice, so the change is visible (and explainable) in the order's timeline:
<?php
namespace Custom\Module\Model;
use Magento\Sales\Api\OrderRepositoryInterface;
class OrderStatusUpdater
{
/**
* @var OrderRepositoryInterface
*/
private $orderRepository;
public function __construct(
OrderRepositoryInterface $orderRepository
) {
$this->orderRepository = $orderRepository;
}
/**
* Change an order's status with a history comment
*
* @param int $orderId
* @param string $newStatus
* @return void
*/
public function changeStatus($orderId, $newStatus)
{
$order = $this->orderRepository->get($orderId);
$order->setStatus($newStatus);
$order->addCommentToStatusHistory(
__('Status changed automatically to %1.', $newStatus),
$newStatus
);
$this->orderRepository->save($order);
}
}
|
(Direct ObjectManager calls like $objectManager->create(OrderRepositoryInterface::class) work in quick standalone test scripts, but in module code always inject dependencies through the constructor as above.)
Automating Status Changes Without Code
Code like this is one status change — real stores usually need a system: custom statuses for every fulfillment stage, rules that switch them automatically, and customers kept in the loop as the order progresses. That's what the Magento 2 Order Status extension handles from the admin panel: create unlimited custom statuses, assign them to states correctly (no workflow-corrupting mistakes possible), and notify customers by email on every change — no controller, cron, or observer to maintain.
Frequently Asked Questions
How to Add a Custom Order Status in Magento 2?
You can do this via the Magento backend:
- Go to Stores > Settings > Order Status.
- Click Create New Status.
- Enter a status code (e.g., ready_for_pickup) and a status label (visible in both the admin and the storefront), then save.
- Then use Assign Status to State on the same page — an unassigned status can't be applied to orders in that state.
What Is the Difference Between "Order State" and "Order Status" in Magento 2?
- Order State is the system workflow stage (new, processing, complete, etc.) — fixed values Magento's logic depends on.
- Order Status is the human-readable label (Pending Payment, Ready for Pickup, etc.) — each status belongs to a state, and you can create as many as you need.
How to Get the Order Status in Magento 2?
You can retrieve the current order status (and state) like this:
$status = $order->getStatus(); $state = $order->getState(); |
Final Thoughts
Changing an order status programmatically in Magento 2 comes down to three lines — load, setStatus, save — plus the one discipline that keeps your order workflow healthy: statuses are yours to change, states belong to Magento's workflow. Add a history comment so every automated change stays traceable, and reach for rule-based automation once one-off scripts stop scaling. Need help building order automation for your store? Get in touch with our Adobe Commerce certified professionals.