Orders & Carts

Variants are added to a cart that can be completed to become an order. Carts and orders are both listed in the control panel under CommerceOrders.

When we use the terms “cart” and “order”, we’re always referring to an Order (opens new window) element; a cart is simply an order that hasn’t been completed—meaning its isCompleted property is false and its dateCompleted is null.

# Carts

As a customer or store manager is building a cart, the goal is to maintain an up-to-date list of items with their relevant costs, discounts, and promotions. For this reason, the cart will be recalculated each time a change is made.

Once a cart is completed, however, it becomes an order that represents choices explicitly finalized by whoever completed the cart. The order’s behavior changes slightly at this point: the customer will no longer be able to make edits, and changes made by a store manager will not automatically trigger recalculation.

Carts and orders are both listed on the Orders index page in the control panel, where you can further limit your view to active carts updated in the last hour, and inactive carts older than an hour that are likely to be abandoned. (You can customize that time limit using the activeCartDuration setting.)

Craft will automatically to purge (delete) abandoned carts after 90 days, and you can customize this behavior with the purgeInactiveCarts and purgeInactiveCartsDuration settings.

On the front end, cart interactions happen via the commerce/cart/update-cart form action.

Here’s what we’ll cover in the sections that follow:

More topics are covered in separate pages:

# Fetching a Cart

In your templates, you can get the current user’s cart like this:

{% set cart = craft.commerce.carts.cart %}

{# Same thing: #}
{% set cart = craft.commerce.getCarts().getCart() %}

You could also fetch the cart via Ajax. This example could be added to a Twig template, and outputs the cart data to the browser’s development console:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$.ajax({
  url: '',
  data: {
    '{{ craft.config.csrfTokenName|e('js') }}': '{{ craft.request.csrfToken|e('js') }}',
    'action': 'commerce/cart/get-cart'
  },
  success: function(response) {
    console.log(response);
  },
  dataType: 'json'
});
</script>

Either of the examples above will generate a new cart in the session if none exists. While it’s unlikely you would make this assignment more than once per page request, getting the cart more than once does not affect performance.

To see what cart information you can use in your templates, take a look at the Order (opens new window) class reference. You can also see sample Twig in the example templates’ shop/cart/index.twig (opens new window).

array (
  'number' => 'e5ab591c5bd77e86b036e8a2d4637238',
  'reference' => 'e5ab591',
  'couponCode' => NULL,
  'isCompleted' => '1',
  'dateOrdered' => array(
    'date' => '12/18/2020',
    'time' => '3:27 PM',
  ),
  'datePaid' => array(
    'date' => '12/18/2020',
    'time' => '3:29 PM',
  ),
  'dateAuthorized' => NULL,
  'currency' => 'USD',
  'gatewayId' => '1',
  'lastIp' => NULL,
  'message' => NULL,
  'returnUrl' => 'https://my-project.tld/admin/commerce/orders/41',
  'cancelUrl' => 'https://my-project.tld/admin/commerce/orders/41',
  'orderStatusId' => '1',
  'orderLanguage' => 'en-US',
  'orderSiteId' => '1',
  'origin' => 'cp',
  'billingAddressId' => '3',
  'shippingAddressId' => '4',
  'makePrimaryShippingAddress' => NULL,
  'makePrimaryBillingAddress' => NULL,
  'shippingSameAsBilling' => NULL,
  'billingSameAsShipping' => NULL,
  'estimatedBillingAddressId' => NULL,
  'estimatedShippingAddressId' => NULL,
  'estimatedBillingSameAsShipping' => NULL,
  'shippingMethodHandle' => 'freeShipping',
  'shippingMethodName' => 'Free Shipping',
  'customerId' => '3',
  'registerUserOnOrderComplete' => NULL,
  'paymentSourceId' => NULL,
  'storedTotalPrice' => '30.0000',
  'storedTotalPaid' => '30.0000',
  'storedItemTotal' => '30.0000',
  'storedItemSubtotal' => '0.0000',
  'storedTotalShippingCost' => '0.0000',
  'storedTotalDiscount' => '0.0000',
  'storedTotalTax' => '0.0000',
  'storedTotalTaxIncluded' => '0.0000',
  'id' => 41,
  'tempId' => NULL,
  'draftId' => NULL,
  'revisionId' => NULL,
  'uid' => '161802df-a7ca-4637-86bd-5a9cd458eefe',
  'siteSettingsId' => 41,
  'fieldLayoutId' => 5,
  'contentId' => 34,
  'enabled' => true,
  'archived' => false,
  'siteId' => 1,
  'title' => NULL,
  'slug' => NULL,
  'uri' => NULL,
  'dateCreated' => array(
    'date' => '12/18/2020',
    'time' => '3:23 PM',
  ),
  'dateUpdated' => array(
    'date' => '12/18/2020',
    'time' => '3:35 PM',
  ),
  'dateDeleted' => NULL,
  'trashed' => false,
  'propagateAll' => false,
  'newSiteIds' => array(),
  'resaving' => false,
  'duplicateOf' => NULL,
  'previewing' => false,
  'hardDelete' => false,
  'ref' => NULL,
  'status' => 'enabled',
  'structureId' => NULL,
  'url' => NULL,
  'adjustmentSubtotal' => 0.0,
  'adjustmentsTotal' => 0.0,
  'paymentCurrency' => 'USD',
  'email' => 'hello@pixelandtonic.com',
  'isPaid' => true,
  'itemSubtotal' => 30.0,
  'itemTotal' => 30.0,
  'lineItems' => array(
    0 => array(
      'id' => 1,
      'weight' => 0.0,
      'length' => 0.0,
      'height' => 0.0,
      'width' => 0.0,
      'qty' => 1,
      'note' => 'Please make a nice one.',
      'privateNote' => '',
      'purchasableId' => '32',
      'orderId' => 41,
      'lineItemStatusId' => NULL,
      'taxCategoryId' => 1,
      'shippingCategoryId' => 1,
      'dateCreated' => '2020-12-18T15:27:26-08:00',
      'dateUpdated' => '2020-12-18T15:35:19-08:00',
      'adjustments' => array(),
      'description' => 'Parka With Stripes On Back',
      'options' => array (
        'engraving' => 'happy-birthday',
        'giftwrap' => 'yes',
      ),
      'optionsSignature' => '85cced2a4add99b2d64abcdb2a6d6a7b',
      'onSale' => false,
      'price' => 30.0,
      'saleAmount' => 0.0,
      'salePrice' => 30.0,
      'sku' => 'PSB-001',
      'total' => 30.0,
      'priceAsCurrency' => '$30.00',
      'saleAmountAsCurrency' => '$0.00',
      'salePriceAsCurrency' => '$30.00',
      'subtotalAsCurrency' => '$30.00',
      'totalAsCurrency' => '$30.00',
      'discountAsCurrency' => '$0.00',
      'shippingCostAsCurrency' => '$0.00',
      'taxAsCurrency' => '$0.00',
      'taxIncludedAsCurrency' => '$0.00',
      'adjustmentsTotalAsCurrency' => '$0.00',
      'subtotal' => 30.0,
    ),
  ),
  'orderAdjustments' => array(),
  'outstandingBalance' => 0.0,
  'paidStatus' => 'paid',
  'recalculationMode' => 'none',
  'shortNumber' => 'e5ab591',
  'totalPaid' => 30.0,
  'total' => 30.0,
  'totalPrice' => 30.0,
  'totalQty' => 1,
  'totalSaleAmount' => 0.0,
  'totalTaxablePrice' => 30.0,
  'totalWeight' => 0.0,
  'adjustmentSubtotalAsCurrency' => '$0.00',
  'adjustmentsTotalAsCurrency' => '$0.00',
  'itemSubtotalAsCurrency' => '$30.00',
  'itemTotalAsCurrency' => '$30.00',
  'outstandingBalanceAsCurrency' => '$0.00',
  'totalPaidAsCurrency' => '$30.00',
  'totalAsCurrency' => '$30.00',
  'totalPriceAsCurrency' => '$30.00',
  'totalSaleAmountAsCurrency' => '$0.00',
  'totalTaxablePriceAsCurrency' => '$30.00',
  'totalTaxAsCurrency' => '$0.00',
  'totalTaxIncludedAsCurrency' => '$0.00',
  'totalShippingCostAsCurrency' => '$0.00',
  'totalDiscountAsCurrency' => '$0.00',
  'storedTotalPriceAsCurrency' => '$30.00',
  'storedTotalPaidAsCurrency' => '$30.00',
  'storedItemTotalAsCurrency' => '$30.00',
  'storedItemSubtotalAsCurrency' => '$0.00',
  'storedTotalShippingCostAsCurrency' => '$0.00',
  'storedTotalDiscountAsCurrency' => '$0.00',
  'storedTotalTaxAsCurrency' => '$0.00',
  'storedTotalTaxIncludedAsCurrency' => '$0.00',
  'paidStatusHtml' => '<span class="commerceStatusLabel"><span class="status green"></span> Paid</span>',
  'customerLinkHtml' => '<span><a href="https://my-project.tld/admin/commerce/customers/3">hello@pixelandtonic.com</a></span>',
  'orderStatusHtml' => '<span class="commerceStatusLabel"><span class="status green"></span> New</span>',
  'totalTax' => 0.0,
  'totalTaxIncluded' => 0.0,
  'totalShippingCost' => 0.0,
  'totalDiscount' => 0.0,
)
expand

Once a cart’s completed and turned into an order, calling craft.commerce.carts.cart again will return a new cart.

# Adding Items to a Cart

You can add purchasables (like a variant) to the cart by submitting post requests to the commerce/cart/update-cart action. Items can be added one at a time or in an array.

# Adding a Single Item

You can add a single item to a cart by submitting its purchasableId.

This gets a product and creates a form that will add its default variant to the cart:

{% set product = craft.products().one() %}
{% set variant = product.defaultVariant %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ hiddenInput('purchasableId', variant.id) }}
  <button type="submit">Add to Cart</button>
</form>

If the product has multiple variants, you could provide a dropdown menu to allow the customer to choose one of them:










 
 
 
 
 



{% set product = craft.products().one() %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ redirectInput('shop/cart') }}
  {{ hiddenInput('successMessage', ('Added "' ~ product.title ~ '" to cart.')|hash) }}
  {{ hiddenInput('qty', 1) }}

  <select name="purchasableId">
    {% for variant in product.variants %}
      <option value="{{ variant.id }}">{{ variant.sku }}</option>
    {% endfor %}
  </select>
  <button type="submit">Add to Cart</button>
</form>

We’re sneaking three new things in here as well:

  1. The successMessage parameter can be used to customize the default “Cart updated.” flash message.
  2. The qty parameter can be used to specify a quantity, which defaults to 1 if not supplied.
  3. Craft’s redirectInput tag can be used to take the user to a specific URL after the cart is updated successfully. If any part of the cart update action fails, the user will not be redirected.

# Adding Multiple Items

You can add multiple purchasables to the cart in a single request using a purchasables array instead of a single purchasableId. This form adds all a product’s variants to the cart at once:







 
 
 
 



{% set product = craft.products().one() %}
<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ redirectInput('shop/cart') }}
  {{ hiddenInput('successMessage', 'Products added to the cart.'|hash) }}
  {% for variant in product.variants %}
     {{ hiddenInput('purchasables[' ~ loop.index ~ '][id]', variant.id) }}
     {{ hiddenInput('purchasables[' ~ loop.index ~ '][qty]', 1) }}
  {% endfor %}
  <button type="submit">Add all variants to cart</button>
</form>

A unique index key is required to group the purchasable id with its qty, and in this example we’re using {{ loop.index }} as a convenient way to provide it.

You can use the purchasableAvailable event to control whether a line item should be available to the current user and cart.

Commerce Lite is limited to a single line in the cart.
If a customer adds a new item, it replaces whatever was already in the cart.
If multiple items are added in a request, only the last one gets added to the cart.

# Working with Line Items

Whenever a purchasable is added to the cart, the purchasable becomes a line item in that cart. In the examples above we set the line item’s quantity with the qty parameter, but we also have note and options to work with.

# Line Item Options and Notes

An options parameter can be submitted with arbitrary data to include with that item. A note parameter can include a message from the customer.

In this example, we’re providing the customer with an option to include a note, preset engraving message, and giftwrap option:

{% set product = craft.products().one() %}
{% set variant = product.defaultVariant %}
<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ hiddenInput('qty', 1) }}
  <input type="text" name="note" value="">
  <select name="options[engraving]">
    <option value="happy-birthday">Happy Birthday</option>
    <option value="good-riddance">Good Riddance</option>
  </select>
  <select name="options[giftwrap]">
    <option value="yes">Yes Please</option>
    <option value="no">No Thanks</option>
  </select>
  {{ hiddenInput('purchasableId', variant.id) }}
  <button type="submit">Add to Cart</button>
</form>

Commerce does not validate the options and note parameters. If you’d like to limit user input, use front-end validation or use the Model::EVENT_DEFINE_RULES (opens new window) event to add validation rules for the LineItem (opens new window) model.

The note and options will be visible on the order’s detail page in the control panel:

Line Item Option Review

# Updating Line Items

The commerce/cart/update-cart endpoint is for both adding and updating cart items.

You can directly modify any line item’s qty, note, and options using that line item’s ID in a lineItems array. Here we’re exposing each line item’s quantity and note for the customer to edit:






 
 




{% set cart = craft.commerce.carts.cart %}
<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {% for item in cart.lineItems %}
    <input type="number" name="lineItems[{{ item.id }}][qty]" min="1" value="{{ item.qty }}">
    <input type="text" name="lineItems[{{ item.id }}][note]" placeholder="My Note" value="{{ item.note }}">
  {% endfor %}
  <button type="submit">Update Line Item</button>
</form>

You can remove a line item by including a remove parameter in the request. This example adds a checkbox the customer can use to remove the line item from the cart:








 




{% set cart = craft.commerce.carts.cart %}
<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {% for item in cart.lineItems %}
    <input type="number" name="lineItems[{{ item.id }}][qty]" min="1" value="{{ item.qty }}">
    <input type="text" name="lineItems[{{ item.id }}][note]" placeholder="My Note" value="{{ item.note }}">
    <input type="checkbox" name="lineItems[{{ item.id }}][remove]" value="1"> Remove item<br>
  {% endfor %}
  <button type="submit">Update Line Item</button>
</form>

The example templates include a detailed cart template (opens new window) for adding and updating items in a full checkout flow.

# Options Uniqueness

If a purchasable is posted that’s identical to one already in the cart, the relevant line item’s quantity will be incremented by the submitted qty.

If you posted a purchasable or tried to update a line item with different options values, however, a new line item will be created instead. This behavior is consistent whether you’re updating one item at a time or multiple items in a single request.

Each line item’s uniqueness is determined behind the scenes by an optionsSignature attribute, which is a hash of the line item’s options. You can think of each line item as being unique based on a combination of its purchasableId and optionsSignature.

The note parameter is not part of a line item’s uniqueness; it will always be updated on a matching line item.

# Line Item Totals

Each line item includes several totals:

  • lineItem.subtotal is the product of the line item’s qty and salePrice.
  • lineItem.adjustmentsTotal is the sum of each of the line item’s adjustment amount values.
  • lineItem.total is the sum of the line item’s subtotal and adjustmentsTotal.

# Loading a Cart

Commerce provides a commerce/cart/load-cart endpoint for loading an existing cart into the current customer’s session.

You can have the user interact with the endpoint by either navigating to a URL or by submitting a form. Either way, the cart number is required.

Each method will store any errors in the session’s error flash data (craft.app.session.getFlash('error')), and the cart being loaded can be active or inactive.

If the desired cart belongs to a user, that user must be logged in to load it into their session.

The loadCartRedirectUrl setting determines where the customer will be sent by default after the cart’s loaded.

# Loading a Cart with a URL

Have the customer navigate to the commerce/cart/load-cart endpoint, including the number parameter in the URL.

A quick way for a store manager to grab the URL is by navigating in the control panel to CommerceOrders, selecting one item from Active Carts or Inactive Carts, and choosing Share cart… from the context menu:

Share cart context menu option

You can also do this from an order edit page by choosing the gear icon and then Share cart….

To do this programmatically, you’ll need to create an absolute URL for the endpoint and include a reference to the desired cart number.

This example sets loadCartUrl to an absolute URL the customer can access to load their cart. Again we’re assuming a cart object already exists for the cart that should be loaded:

{% set loadCartUrl = actionUrl(
  'commerce/cart/load-cart',
  { number: cart.number }
) %}

This URL can be presented to the user however you’d like. It’s particularly useful in an email that allows the customer to retrieve an abandoned cart.

# Loading a Cart with a Form

Send a GET or POST action request with a number parameter referencing the cart you’d like to load. When posting the form data, you can include a specific redirect location like you can with any other Craft post data.

This is a simplified version of shop/cart/load.twig (opens new window) in the example templates, where a cart variable has already been set to the cart that should be loaded:

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/load-cart') }}
  {{ redirectInput('/shop/cart') }}

  <input type="text" name="number" value="{{ cart.number }}">
  <button type="submit">Submit</button>
</form>

# Restoring Previous Cart Contents

If the customer’s a registered user they may want to continue shopping from another browser or computer. If that customer has an empty cart—as they would by default—and they log into the site, the cart from a previous session will automatically be loaded.

You can allow a customer to see carts from previous logged-in sessions:

{% if currentUser %}
  {% set currentCart = craft.commerce.carts.cart %}
  {% if currentCart.id %}
    {# Return all incomplete carts *except* the one from this session #}
    {% set oldCarts = craft.orders()
      .isCompleted(false)
      .id('not '~currentCart.id)
      .user(currentUser)
      .all() %}
  {% else %}
    {% set oldCarts = craft.orders()
      .isCompleted(false)
      .user(currentUser)
      .all() %}
  {% endif %}
{% endif %}

You could then loop over the line items in those older carts and allow the customer to add them to the current order:

<h2>Previous Cart Items</h2>

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {% for oldCart in oldCarts %}
    {% for lineItem in oldCart.lineItems %}
      {{ lineItem.description }}
      <label>
        {{ input('checkbox', 'purchasables[][id]', lineItem.getPurchasable().id) }}
        Add to cart
      </label>
    {% endfor %}
  {% endfor %}

  <button type="submit">Update Cart</button>
</form>

# Forgetting a Cart

A logged-in customer’s cart is removed from their session automatically when they log out. You can call the forgetCart() method directly at any time to remove the current cart from the session. The cart itself will not be deleted, but only disassociated with the active session.

{# Forget the cart in the current session. #}
{{ craft.commerce.carts.forgetCart() }}

# Orders

Orders are Element Types and can have custom fields associated with them. You can browse orders in the control panel by navigating to CommerceOrders.

When a cart becomes an order, the following things happen:

  1. The dateOrdered order attribute is set to the current date.
  2. The isCompleted order attribute is set to true.
  3. The default order status is set on the order and any emails for this status are sent.
  4. The order reference number is generated for the order, based on the “Order Reference Number Format” setting. (In the control panel: CommerceSettingsSystem SettingsGeneral Settings.)

Instead of being recalculated on each change like a cart, the order will only be recalculated if you manually trigger recalculation.

Adjustments for discounts, shipping, and tax may be applied when an order is recalcuated. Each adjustment is related to its order, and can optionally relate to a specific line item.

If you’d like to jump straight to displaying order information in your templates, take a look at the the craft\commerce\elements\Order (opens new window) class reference for a complete list of available properties.

# Order Numbers

There are three ways to identify an order: by order number, short order number, and order reference number.

# Order Number

The order number is a hash generated when the cart is first created in the user’s session. It exists even before the cart is saved in the database, and remains the same for the entire life of the order.

This is different from the order reference number, which is only generated after the cart has been completed and becomes an order.

We recommend using the order number when referencing the order in URLs or anytime the order is retrieved publicly.

# Short Order Number

The short order number is the first seven characters of the order number. This is short enough to still be unique, and is a little friendlier to customers, although not as friendly as the order reference number.

# Order Reference Number

The order reference number is generated on cart completion according to the Order Reference Number Format in CommerceSettingsSystem SettingsGeneral Settings.

This number is usually best used as the customer-facing identifier of the order, but shouldn’t be used in URLs.

{{ object.reference }}

The “Order Reference Number Format” is a mini Twig template that’s rendered when the order is completed. It can use order attributes along with Twig filters and functions. For example:

{{ object.dateCompleted|date('Y') }}-{{ id }}

Output:

2018-43

In this example, {{ id }} refers to the order’s element ID, which is not sequential. If you would rather generate a unique sequential number, a simple way would be to use Craft’s seq() (opens new window) Twig function, which generates a next unique number based on the name parameter passed to it.

The seq() function takes the following parameters:

  1. A key name. If this name is changed, a new sequence starting at one is started.
  2. An optional padding character length. For example if the next sequence number is 14 and the padding length is 8, the generation number will be 00000014

For example:

{{ object.dateCompleted|date('Y') }}-{{ seq(object.dateCompleted|date('Y'), 8) }}

Ouput:

2018-00000023

In this example we’ve used the year as the sequence name so we automatically get a new sequence, starting at 1, when the next year arrives.

# Creating Orders

An order is usually created on the front end as a customer adds items to and completes a cart. With Commerce Pro, An order may also be created in the control panel.

To create a new order, navigate to CommerceOrders, and choose New Order. This will create a new order that behaves like a cart. As purchasables are added and removed from the order, it will automatically recalculate its sales and adjustments.

You must be using Commerce Pro and have “Edit Orders” permission to create orders from the control panel.

To complete the order, press Mark as completed.

# Editing Orders

Orders can be edited in the control panel by visiting the order edit page and choosing Edit. You’ll enter edit mode where you can change values within the order.

While editing the order, it will refresh subtotals and totals and display any errors. It will not automatically recalculate the order based on system rules like shipping, taxes, or promotions. Choose Recalculate order to have it fully recalculate including those system rules.

Once you’re happy with your changes, choose Update Order to save it to the database.

# Order Totals

Every order includes a few important totals:

  • order.itemSubtotal is the sum of the order’s line item subtotal amounts.
  • order.itemTotal is the sum of the order’s line item total amounts.
  • order.adjustmentSubtotal is the sum of the order’s adjustments.
  • order.total is the sum of the order’s itemSubtotal and adjustmentsTotal.
  • order.totalPrice is the total order price with a minimum enforced by the minimumTotalPriceStrategy setting.

You’ll also find an order.adjustmentsSubtotal which is identical to order.adjustmentsTotal. It will be removed in Commerce 4.

# Recalculating Orders

Let’s take a closer look at how carts and orders can be recalculated.

A cart or order must always be in one of three calculation modes:

  • Recalculate All is active for a cart, or an order which is not yet completed.
    This mode refreshes each line item’s details from the related purchasable and re-applies all adjustments to the cart. In other words, it rebuilds and recalculates cart details based on information from purchasables and how Commerce is configured. This mode merges duplicate line items and removes any that are out of stock or whose purchasables were deleted.
  • Adjustments Only doesn’t touch line items, but re-applies adjustments on the cart or order. This can only be set programmatically and is not available from the control panel.
  • None doesn’t change anything at all, and is active for an order (completed cart) so only manual edits or admin-initiated recalculation can modify order details.

Cart/order subtotals and totals are computed values that always reflect the sum of line items. A few examples are totalPrice, itemTotal, and itemSubtotal.

You can manually recalculate an order by choosing “Recalculate order” at the bottom of the order edit screen:

This will set temporarily the order’s calculation mode to Recalculate All and trigger recalculation. You can then apply the resulting changes to the order by choosing “Update Order”, or discard them by choosing “Cancel”.

# Order Notices

Notices are added to an order whenever it changes, whether it’s the customer saving the cart or a store manager recalculating from the control panel. Each notice is an OrderNotice (opens new window) model describing what changed and could include the following:

  • A previously-valid coupon or shipping method was removed from the order.
  • A line item’s purchasable was no longer available so that line item was removed from the cart.
  • A line item’s sale price changed for some reason, like the sale no longer applied for example.

The notice includes a human-friendly terms that can be exposed to the customer, and references to the order and attribute it’s describing.

# Accessing Order Notices

Notices are stored on the order and accessed with getNotices() and getFirstNotice() methods.

Each can take optional type and attribute parameters to limit results to a certain order attribute or kind of notice.

A notice’s type will be one of the following:

  • lineItemSalePriceChanged
  • lineItemRemoved
  • shippingMethodChanged
  • invalidCouponRemoved
{# @var order craft\commerce\elements\Order #}

{# Get a multi-dimensional array of notices by attribute key #}
{% set notices = order.getNotices() %}

{# Get an array of notice models for the `couponCode` attribute only #}
{% set couponCodeNotices = order.getNotices(null, 'couponCode') %}

{# Get the first notice only for the `couponCode` attribute #}
{% set firstCouponCodeNotice = order.getFirstNotice(null, 'couponCode') %}

{# Get an array of notice models for changed line item prices #}
{% set priceChangeNotices = order.getNotices('lineItemSalePriceChanged') %}

Each notice can be output as a string in a template:

{% set notice = order.getFirstNotice('salePrice') %}
<p>{{ notice }}</p>
{# result: <p>The price of x has changed</p> #}

# Clearing Order Notices

Notices remain on an order until they’re cleared. You can clear all notices by posting to the cart update form action, or call the order’s clearNotices() method to clear specific notices or all of them at once.

This example clears all the notices on the cart:





 



<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ hiddenInput('successMessage', 'All notices dismissed'|hash) }}
  {{ hiddenInput('clearNotices') }}
  <button type="submit">Dismiss</button>
</form>

This only clears couponCode notices on the cart:

{# Clear notices on the `couponCode` attribute #}
{% do cart.clearNotices(null, 'couponCode') %}

The notices will be cleared from the cart object in memory. If you’d like the cleared notices to persist, you’ll need to save the cart:

{# Save the cart #}
{% do craft.app.elements.saveElement(cart) %}

# Order Status Emails

If status emails are set up for a newly-updated order status, messages will be sent when the updated order is saved.

You can manually re-send an order status email at any time. Navigate to an order’s edit page, then select the desired email from the Send Email menu at the top of the page.

# Querying Orders

You can fetch orders in your templates or PHP code using order queries.

{# Create a new order query #}
{% set myOrderQuery = craft.orders() %}

Once you’ve created an order query, you can set parameters on it to narrow down the results, and then execute it (opens new window) by calling .all(). An array of Order (opens new window) objects will be returned.

See Element Queries (opens new window) in the Craft docs to learn about how element queries work.

# Example

We can display an order with a given order number by doing the following:

  1. Create an order query with craft.orders().
  2. Set the number parameter on it.
  3. Fetch the order with .one().
  4. Output information about the order as HTML.
{# Get the requested order number from the query string #}
{% set orderNumber = craft.app.request.getQueryParam('number') %}

{# Create an order query with the 'number' parameter #}
{% set myOrderQuery = craft.orders()
  .number(orderNumber) %}

{# Fetch the order #}
{% set order = myOrderQuery.one() %}

{# Make sure it exists #}
{% if not order %}
  {% exit 404 %}
{% endif %}

{# Display the order #}
<h1>Order {{ order.getShortNumber() }}</h1>
<!-- ... ->

# Order Query Parameters

Order queries support the following parameters:

Param Description
afterPopulate Performs any post-population processing on elements.
andRelatedTo Narrows the query results to only orders that are related to certain other elements.
anyStatus Removes element filters based on their statuses.
asArray Causes the query to return matching orders as arrays of data, rather than Order (opens new window) objects.
cache Enables query cache for this Query.
clearCachedResult Clears the cached result (opens new window).
customer Narrows the query results based on the customer.
customerId Narrows the query results based on the customer, per their ID.
dateAuthorized Narrows the query results based on the orders’ authorized dates.
dateCreated Narrows the query results based on the orders’ creation dates.
dateOrdered Narrows the query results based on the orders’ completion dates.
datePaid Narrows the query results based on the orders’ paid dates.
dateUpdated Narrows the query results based on the orders’ last-updated dates.
email Narrows the query results based on the customers’ email addresses.
expiryDate Narrows the query results based on the orders’ expiry dates.
fixedOrder Causes the query results to be returned in the order specified by id.
gateway Narrows the query results based on the gateway.
gatewayId Narrows the query results based on the gateway, per its ID.
hasLineItems Narrows the query results to only orders that have line items.
hasPurchasables Narrows the query results to only orders that have certain purchasables.
hasTransactions Narrows the query results to only carts that have at least one transaction.
id Narrows the query results based on the orders’ IDs.
ignorePlaceholders Causes the query to return matching orders as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window).
inReverse Causes the query results to be returned in reverse order.
isCompleted Narrows the query results to only orders that are completed.
isPaid Narrows the query results to only orders that are paid.
isUnpaid Narrows the query results to only orders that are not paid.
limit Determines the number of orders that should be returned.
number Narrows the query results based on the order number.
offset Determines how many orders should be skipped in the results.
orderBy Determines the order that the orders should be returned in. (If empty, defaults to id ASC.)
orderLanguage Narrows the query results based on the order language, per the language string provided.
orderSiteId Narrows the query results based on the order language, per the language string provided.
orderStatus Narrows the query results based on the order statuses.
orderStatusId Narrows the query results based on the order statuses, per their IDs.
origin Narrows the query results based on the origin.
preferSites If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements.
reference Narrows the query results based on the order reference.
relatedTo Narrows the query results to only orders that are related to certain other elements.
search Narrows the query results to only orders that match a search query.
shortNumber Narrows the query results based on the order short number.
siteSettingsId Narrows the query results based on the orders’ IDs in the elements_sites table.
trashed Narrows the query results to only orders that have been soft-deleted.
uid Narrows the query results based on the orders’ UIDs.
user Narrows the query results based on the customer’s user account.
with Causes the query to return matching orders eager-loaded with related elements.
withAddresses Eager loads the the shipping and billing addressees on the resulting orders.
withAdjustments Eager loads the order adjustments on the resulting orders.
withAll Eager loads all relational data (addresses, adjustents, customers, line items, transactions) for the resulting orders.
withCustomer Eager loads the customer (and related user) on the resulting orders.
withLineItems Eager loads the line items on the resulting orders.
withTransactions Eager loads the transactions on the resulting orders.

# afterPopulate

Performs any post-population processing on elements.

# andRelatedTo

Narrows the query results to only orders that are related to certain other elements.

See Relations (opens new window) for a full explanation of how to work with this parameter.

{# Fetch all orders that are related to myCategoryA and myCategoryB #}
{% set orders = craft.orders()
  .relatedTo(myCategoryA)
  .andRelatedTo(myCategoryB)
  .all() %}

# anyStatus

Removes element filters based on their statuses.

{# Fetch all orders, regardless of status #}
{% set orders = craft.orders()
  .anyStatus()
  .all() %}

# asArray

Causes the query to return matching orders as arrays of data, rather than Order (opens new window) objects.

{# Fetch orders as arrays #}
{% set orders = craft.orders()
  .asArray()
  .all() %}

# cache

Enables query cache for this Query.

# clearCachedResult

Clears the cached result (opens new window).

# customer

Narrows the query results based on the customer.

Possible values include:

Value Fetches orders…
a Customer (opens new window) object with a customer represented by the object.
{# Fetch the current user's orders #}
{% set orders = craft.orders()
  .customer(currentUser.customerFieldHandle)
  .all() %}

# customerId

Narrows the query results based on the customer, per their ID.

Possible values include:

Value Fetches orders…
1 with a customer with an ID of 1.
'not 1' not with a customer with an ID of 1.
[1, 2] with a customer with an ID of 1 or 2.
['not', 1, 2] not with a customer with an ID of 1 or 2.
{# Fetch the current user's orders #}
{% set orders = craft.orders()
  .customerId(currentUser.customerFieldHandle.id)
  .all() %}

# dateAuthorized

Narrows the query results based on the orders’ authorized dates.

Possible values include:

Value Fetches orders…
'>= 2018-04-01' that were authorized on or after 2018-04-01.
'< 2018-05-01' that were authorized before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were completed between 2018-04-01 and 2018-05-01.
{# Fetch orders that were authorized recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set orders = craft.orders()
  .dateAuthorized(">= #{aWeekAgo}")
  .all() %}

# dateCreated

Narrows the query results based on the orders’ creation dates.

Possible values include:

Value Fetches orders…
'>= 2018-04-01' that were created on or after 2018-04-01.
'< 2018-05-01' that were created before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were created between 2018-04-01 and 2018-05-01.
{# Fetch orders created last month #}
{% set start = date('first day of last month')|atom %}
{% set end = date('first day of this month')|atom %}

{% set orders = craft.orders()
  .dateCreated(['and', ">= #{start}", "< #{end}"])
  .all() %}

# dateOrdered

Narrows the query results based on the orders’ completion dates.

Possible values include:

Value Fetches orders…
'>= 2018-04-01' that were completed on or after 2018-04-01.
'< 2018-05-01' that were completed before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were completed between 2018-04-01 and 2018-05-01.
{# Fetch orders that were completed recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set orders = craft.orders()
  .dateOrdered(">= #{aWeekAgo}")
  .all() %}

# datePaid

Narrows the query results based on the orders’ paid dates.

Possible values include:

Value Fetches orders…
'>= 2018-04-01' that were paid on or after 2018-04-01.
'< 2018-05-01' that were paid before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were completed between 2018-04-01 and 2018-05-01.
{# Fetch orders that were paid for recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set orders = craft.orders()
  .datePaid(">= #{aWeekAgo}")
  .all() %}

# dateUpdated

Narrows the query results based on the orders’ last-updated dates.

Possible values include:

Value Fetches orders…
'>= 2018-04-01' that were updated on or after 2018-04-01.
'< 2018-05-01' that were updated before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were updated between 2018-04-01 and 2018-05-01.
{# Fetch orders updated in the last week #}
{% set lastWeek = date('1 week ago')|atom %}

{% set orders = craft.orders()
  .dateUpdated(">= #{lastWeek}")
  .all() %}

# email

Narrows the query results based on the customers’ email addresses.

Possible values include:

Value Fetches orders with customers…
'foo@bar.baz' with an email of foo@bar.baz.
'not foo@bar.baz' not with an email of foo@bar.baz.
'*@bar.baz' with an email that ends with @bar.baz.
{# Fetch orders from customers with a .co.uk domain on their email address #}
{% set orders = craft.orders()
  .email('*.co.uk')
  .all() %}

# expiryDate

Narrows the query results based on the orders’ expiry dates.

Possible values include:

Value Fetches orders…
'>= 2020-04-01' that will expire on or after 2020-04-01.
'< 2020-05-01' that will expire before 2020-05-01
['and', '>= 2020-04-04', '< 2020-05-01'] that will expire between 2020-04-01 and 2020-05-01.
{# Fetch orders expiring this month #}
{% set nextMonth = date('first day of next month')|atom %}

{% set orders = craft.orders()
  .expiryDate("< #{nextMonth}")
  .all() %}

# fixedOrder

Causes the query results to be returned in the order specified by id.

{# Fetch orders in a specific order #}
{% set orders = craft.orders()
  .id([1, 2, 3, 4, 5])
  .fixedOrder()
  .all() %}

# gateway

Narrows the query results based on the gateway.

Possible values include:

Value Fetches orders…
a Gateway (opens new window) object with a gateway represented by the object.

# gatewayId

Narrows the query results based on the gateway, per its ID.

Possible values include:

Value Fetches orders…
1 with a gateway with an ID of 1.
'not 1' not with a gateway with an ID of 1.
[1, 2] with a gateway with an ID of 1 or 2.
['not', 1, 2] not with a gateway with an ID of 1 or 2.

# hasLineItems

Narrows the query results to only orders that have line items.

{# Fetch orders that do or do not have line items #}
{% set orders = craft.orders()
  .hasLineItems()
  .all() %}

# hasPurchasables

Narrows the query results to only orders that have certain purchasables.

Possible values include:

Value Fetches orders…
a PurchasableInterface (opens new window) object with a purchasable represented by the object.
an array of PurchasableInterface (opens new window) objects with all the purchasables represented by the objects.

# hasTransactions

Narrows the query results to only carts that have at least one transaction.

{# Fetch carts that have attempted payments #}
{% set orders = craft.orders()
  .hasTransactions()
  .all() %}

# id

Narrows the query results based on the orders’ IDs.

Possible values include:

Value Fetches orders…
1 with an ID of 1.
'not 1' not with an ID of 1.
[1, 2] with an ID of 1 or 2.
['not', 1, 2] not with an ID of 1 or 2.
{# Fetch the order by its ID #}
{% set order = craft.orders()
  .id(1)
  .one() %}

This can be combined with fixedOrder if you want the results to be returned in a specific order.

# ignorePlaceholders

Causes the query to return matching orders as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window).

# inReverse

Causes the query results to be returned in reverse order.

{# Fetch orders in reverse #}
{% set orders = craft.orders()
  .inReverse()
  .all() %}

# isCompleted

Narrows the query results to only orders that are completed.

{# Fetch completed orders #}
{% set orders = craft.orders()
  .isCompleted()
  .all() %}

# isPaid

Narrows the query results to only orders that are paid.

{# Fetch paid orders #}
{% set orders = craft.orders()
  .isPaid()
  .all() %}

# isUnpaid

Narrows the query results to only orders that are not paid.

{# Fetch unpaid orders #}
{% set orders = craft.orders()
  .isUnpaid()
  .all() %}

# limit

Determines the number of orders that should be returned.

{# Fetch up to 10 orders  #}
{% set orders = craft.orders()
  .limit(10)
  .all() %}

# number

Narrows the query results based on the order number.

Possible values include:

Value Fetches orders…
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' with a matching order number
{# Fetch the requested order #}
{% set orderNumber = craft.app.request.getQueryParam('number') %}
{% set order = craft.orders()
  .number(orderNumber)
  .one() %}

# offset

Determines how many orders should be skipped in the results.

{# Fetch all orders except for the first 3 #}
{% set orders = craft.orders()
  .offset(3)
  .all() %}

# orderBy

Determines the order that the orders should be returned in. (If empty, defaults to id ASC.)

{# Fetch all orders in order of date created #}
{% set orders = craft.orders()
  .orderBy('dateCreated ASC')
  .all() %}

# orderLanguage

Narrows the query results based on the order language, per the language string provided.

Possible values include:

Value Fetches orders…
en with an order language that is 'en'.
'not en' not with an order language that is not 'en'.
['en', 'en-us'] with an order language that is 'en' or 'en-us'.
['not', 'en'] not with an order language that is not 'en'.
{# Fetch orders with an order status with an ID of 1 #}
{% set orders = craft.orders()
  .orderLanguage('en')
  .all() %}

# orderSiteId

Narrows the query results based on the order language, per the language string provided.

Possible values include:

Value Fetches orders…
1 with an order site ID of 1.
'not 1' not with an order site ID that is no 1.
[1, 2] with an order site ID of 1 or 2.
['not', 1, 2] not with an order site ID of 1 or 2.
{# Fetch orders with an order site ID of 1 #}
{% set orders = craft.orders()
  .orderSiteId(1)
  .all() %}

# orderStatus

Narrows the query results based on the order statuses.

Possible values include:

Value Fetches orders…
'foo' with an order status with a handle of foo.
'not foo' not with an order status with a handle of foo.
['foo', 'bar'] with an order status with a handle of foo or bar.
['not', 'foo', 'bar'] not with an order status with a handle of foo or bar.
a OrderStatus (opens new window) object with an order status represented by the object.
{# Fetch shipped orders #}
{% set orders = craft.orders()
  .orderStatus('shipped')
  .all() %}

# orderStatusId

Narrows the query results based on the order statuses, per their IDs.

Possible values include:

Value Fetches orders…
1 with an order status with an ID of 1.
'not 1' not with an order status with an ID of 1.
[1, 2] with an order status with an ID of 1 or 2.
['not', 1, 2] not with an order status with an ID of 1 or 2.
{# Fetch orders with an order status with an ID of 1 #}
{% set orders = craft.orders()
  .orderStatusId(1)
  .all() %}

# origin

Narrows the query results based on the origin.

Possible values include:

Value Fetches orders…
'web' with an origin of web.
'not remote' not with an origin of remote.
['web', 'cp'] with an order origin of web or cp.
['not', 'remote', 'cp'] not with an origin of web or cp.
{# Fetch shipped orders #}
{% set orders = craft.orders()
  .origin('web')
  .all() %}

# preferSites

If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements.

For example, if element “Foo” exists in Site A and Site B, and element “Bar” exists in Site B and Site C, and this is set to ['c', 'b', 'a'], then Foo will be returned for Site C, and Bar will be returned for Site B.

If this isn’t set, then preference goes to the current site.

{# Fetch unique orders from Site A, or Site B if they don’t exist in Site A #}
{% set orders = craft.orders()
  .site('*')
  .unique()
  .preferSites(['a', 'b'])
  .all() %}

# reference

Narrows the query results based on the order reference.

Possible values include:

Value Fetches orders…
'xxxx' with a matching order reference
{# Fetch the requested order #}
{% set orderReference = craft.app.request.getQueryParam('ref') %}
{% set order = craft.orders()
  .reference(orderReference)
  .one() %}

# relatedTo

Narrows the query results to only orders that are related to certain other elements.

See Relations (opens new window) for a full explanation of how to work with this parameter.

{# Fetch all orders that are related to myCategory #}
{% set orders = craft.orders()
  .relatedTo(myCategory)
  .all() %}

Narrows the query results to only orders that match a search query.

See Searching (opens new window) for a full explanation of how to work with this parameter.

{# Get the search query from the 'q' query string param #}
{% set searchQuery = craft.app.request.getQueryParam('q') %}

{# Fetch all orders that match the search query #}
{% set orders = craft.orders()
  .search(searchQuery)
  .all() %}

# shortNumber

Narrows the query results based on the order short number.

Possible values include:

Value Fetches orders…
'xxxxxxx' with a matching order number
{# Fetch the requested order #}
{% set orderNumber = craft.app.request.getQueryParam('shortNumber') %}
{% set order = craft.orders()
  .shortNumber(orderNumber)
  .one() %}

# siteSettingsId

Narrows the query results based on the orders’ IDs in the elements_sites table.

Possible values include:

Value Fetches orders…
1 with an elements_sites ID of 1.
'not 1' not with an elements_sites ID of 1.
[1, 2] with an elements_sites ID of 1 or 2.
['not', 1, 2] not with an elements_sites ID of 1 or 2.
{# Fetch the order by its ID in the elements_sites table #}
{% set order = craft.orders()
  .siteSettingsId(1)
  .one() %}

# trashed

Narrows the query results to only orders that have been soft-deleted.

{# Fetch trashed orders #}
{% set orders = craft.orders()
  .trashed()
  .all() %}

# uid

Narrows the query results based on the orders’ UIDs.

{# Fetch the order by its UID #}
{% set order = craft.orders()
  .uid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
  .one() %}

# user

Narrows the query results based on the customer’s user account.

Possible values include:

Value Fetches orders…
1 with a customer with a user account ID of 1.
a User (opens new window) object with a customer with a user account represented by the object.
{# Fetch the current user's orders #}
{% set orders = craft.orders()
  .user(currentUser)
  .all() %}

# with

Causes the query to return matching orders eager-loaded with related elements.

See Eager-Loading Elements (opens new window) for a full explanation of how to work with this parameter.

{# Fetch orders eager-loaded with the "Related" field’s relations #}
{% set orders = craft.orders()
  .with(['related'])
  .all() %}

# withAddresses

Eager loads the the shipping and billing addressees on the resulting orders.

Possible values include:

Value Fetches addresses
bool true to eager-load, false to not eager load.

# withAdjustments

Eager loads the order adjustments on the resulting orders.

Possible values include:

Value Fetches adjustments
bool true to eager-load, false to not eager load.

# withAll

Eager loads all relational data (addresses, adjustents, customers, line items, transactions) for the resulting orders.

Possible values include:

Value Fetches addresses, adjustents, customers, line items, transactions
bool true to eager-load, false to not eager load.

# withCustomer

Eager loads the customer (and related user) on the resulting orders.

Possible values include:

Value Fetches adjustments
bool true to eager-load, false to not eager load.

# withLineItems

Eager loads the line items on the resulting orders.

Possible values include:

Value Fetches line items…
bool true to eager-load, false to not eager load.

# withTransactions

Eager loads the transactions on the resulting orders.

Possible values include:

Value Fetches transactions…
bool true to eager-load, false to not eager load.
Parts of this page are generated or assembled by automations. While we greatly appreciate contributions to the documentation, reporting automated content issues will allow us to fix them at the source!