Catalog Price Rules vs. Cart Price Rules in Magento 2: Which Rule Applies When?
AI generated
M2
di.xml
Magento 2 · CatalogRule · SalesRule · Pricing Rules
Catalog Price Rules vs. Cart Price Rules in Magento 2
two rule systems, one practical decision tree

Catalog Price Rules and Cart Price Rules solve different problems in Magento 2, yet they are frequently confused or combined incorrectly on real projects. Understanding the indexing, the point of application, and the customer group logic of both rule systems prevents double discounting and unnecessary reindex overhead. This article lays out the practical decision tree: when a Catalog Price Rule and when a Cart Price Rule is the right tool.

18 min read Catalog Price Rules · Cart Price Rules · Indexing Magento 2.4.8-p4 · PHP 8.4 · GraphQL

1. Two Rule Systems in Magento 2: Catalog Price Rules and Cart Price Rules at a Glance

Magento 2 ships two separate discount mechanisms: Catalog Price Rules from the Magento_CatalogRule module and Cart Price Rules from the Magento_SalesRule module. Both produce price reductions, but they do so in fundamentally different ways and at different points in time. Catalog Price Rules change the displayed product price already in the catalog, before a customer has even added anything to the cart. Cart Price Rules, on the other hand, only apply at runtime in the cart or checkout and frequently require a coupon code or a fulfilled condition.

The module names already reveal the architecture: Magento_CatalogRule works with its own indexer that pre-computes discounts and stores them in a dedicated table. Magento_SalesRule skips indexing entirely and evaluates every rule live during price calculation on the quote object. Once you understand both systems, it immediately makes sense why a catalog discount is visible in the product grid right away, while a cart discount only shows up after the item has been added to the cart.

In practice, Catalog Price Rules and Cart Price Rules are frequently confused because both are managed under "Marketing" in the admin panel and use similar condition editors. The conditions look almost identical in the UI, yet the underlying execution differs fundamentally. This article compares both rule systems across indexing, data model, performance and decision logic, so the choice between a Catalog Price Rule and a Cart Price Rule is made deliberately on every project.

2. Catalog Price Rules in Detail: Indexing, Application Point and Price Display in the Catalog

A Catalog Price Rule defines a condition, for example category, attribute or customer group, and an action: percentage discount, fixed amount or fixed price. Once saved, the catalogrule_rule indexer resolves the rule. For every combination of product, customer group, website and date, the resulting price is calculated in advance. The result lands in the catalogrule_product_price table, from which product listings, product detail pages and the catalog_product_index_price price index all draw their values. The customer therefore sees the reduced price before they even click on the product.

This pre-computation has a decisive advantage: displaying the price in the catalog costs no additional compute time at runtime, because the price already sits in the index. The downside shows up when saving a rule or during import runs: the catalogrule indexer has to run again for every affected product-customer-group-website combination, which can take several minutes on large catalogs with many customer groups. Without a timely reindex, the catalog shows stale prices, even though the rule is already marked active in the admin panel.

Over the GraphQL interface, the price reduced by a Catalog Price Rule shows up in the final_price field, while regular_price returns the undiscounted base price. The discount field exposes the difference between both values so the client does not have to calculate anything itself. Since the calculation happens entirely inside the indexer, GraphQL returns the same pre-computed price that the storefront grid displays, consistently across every access path.


# GraphQL query showing catalog price already reduced by a Catalog Price Rule
query CatalogPriceExample {
  products(filter: { sku: { eq: "24-MB01" } }) {
    items {
      sku
      price_range {
        minimum_price {
          # Regular price without any Catalog Price Rule applied
          regular_price {
            value
            currency
          }
          # Final price already includes the active Catalog Price Rule
          final_price {
            value
            currency
          }
          discount {
            amount_off
            percent_off
          }
        }
      }
    }
  }
}

3. Cart Price Rules in Detail: Runtime Calculation in Checkout, Coupons and Customer Group Conditions

A Cart Price Rule is not pre-indexed, it is evaluated live on every price calculation of the cart. As soon as a customer adds a product to the cart or enters checkout, Magento checks every active Cart Price Rule against the current quote: customer group, cart contents, order total, shipping method and, where applicable, an entered coupon code. Only when all conditions are met is the configured action, for example a percentage discount, a fixed amount, or free shipping, applied.

Coupons are a domain exclusively covered by Cart Price Rules. Catalog Price Rules have no coupon mechanism, because they are already resolved in the index before any customer interaction occurs. Cart Price Rules, in contrast, support both automatic rules without a coupon and rules with a single fixed coupon code, or with automatically generated coupon sets for campaigns such as newsletter sign-ups or affiliate promotions.

Customer group conditions work more granularly with Cart Price Rules, because the full order context is available at runtime: billing address, shipping country, other rules already applied, and the sort order in which multiple Cart Price Rules are evaluated. This runtime flexibility explains why complex B2B special terms, quantity-based tiered discounts, or time-limited promo codes are almost always implemented as a Cart Price Rule rather than a Catalog Price Rule.

4. Data Model Differences: catalogrule vs. salesrule Entity, Indexer Architecture

The catalogrule entity lives in the catalogrule and catalogrule_website tables, while the mapping to products is materialized in catalogrule_product. The actual price per combination sits in catalogrule_product_price, complemented by catalogrule_group_website, which maps validity per customer group and website. This structure is deliberately denormalized so that read access when rendering product listings can happen without joins across the condition logic.

The salesrule entity, on the other hand, lives in salesrule, salesrule_website, salesrule_customer_group, and, where coupons are used, in salesrule_coupon and salesrule_coupon_usage. There is no price table, because salesrule does not pre-compute any prices, it only stores rule definitions and conditions, serialized in salesrule.conditions_serialized. The actual calculation happens exclusively at runtime inside Magento\SalesRule\Model\RulesApplier.

The XML configuration in indexer.xml declares two indexers for Catalog Price Rules: catalogrule_rule computes the rule-to-product mapping, catalogrule_product updates the derived product index. Cart Price Rules deliberately have no entry in indexer.xml, because there is no index that could ever become invalid. This architectural decision is the core of the entire difference between both rule systems.


<?xml version="1.0"?>
<!-- Indexer declarations exist only for Catalog Price Rules, Cart Price Rules have none -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd">
    <indexer id="catalogrule_rule"
             view_id="catalogrule_rule"
             class="Magento\CatalogRule\Model\Indexer\IndexBuilder">
        <title translate="true">Catalog Rule Product Rules</title>
        <description translate="true">Rebuild the catalog rule to product mapping</description>
    </indexer>
    <indexer id="catalogrule_product"
             view_id="catalogrule_product"
             class="Magento\CatalogRule\Model\Indexer\Product\ProductRuleProcessor">
        <title translate="true">Catalog Rule Product</title>
        <description translate="true">Rebuild the catalog rule product price index</description>
    </indexer>
</config>

5. Which Rule Applies When: a Practical Decision Tree

The decision between a Catalog Price Rule and a Cart Price Rule can be made with just a few questions. Should the discount be visible to every eligible customer without any action on their part, the moment they open the product page? Then a Catalog Price Rule is the right choice, for example for a permanent B2B special price or a promotion that should already be visible in the grid. Does the action instead require a coupon code, a minimum order total, or a combination of multiple cart line items? Then a Cart Price Rule is the right tool, because only it can access the full cart contents at runtime.

The following table summarizes the key decision criteria. Anyone planning a rule should first check whether the discount needs to be visible before the item is even added to the cart, because that is something only a Catalog Price Rule can deliver. Cart Price Rules are the right fit whenever coupons, shipping costs, or cross-cart conditions, such as "3 for 2", are part of the action.

Dimension Catalog Price Rules Cart Price Rules
Application point Already in the catalog, before the cart Only in the cart or checkout
Indexing required Yes, catalogrule_rule and catalogrule_product No, pure runtime calculation
Coupon support Not possible Yes, fixed or auto-generated
Customer group targeting Yes, via catalogrule_group_website Yes, plus full quote context
Performance cost At reindex time, not on page load On every cart / checkout request
Typical use case Permanent special price, B2B price list Coupon promotion, time-limited campaign

In mixed scenarios, for example a permanent reseller special price combined with a time-limited newsletter promotion, both rule systems run in parallel. The B2B special price runs as a Catalog Price Rule, the newsletter promotion as a coupon-based Cart Price Rule. This combination is common and works reliably as long as the order and possible overlaps are respected, as the next section shows.

6. Combining Both Rule Systems and Avoiding Conflicts

When Catalog Price Rules and Cart Price Rules apply to the same product at the same time, the discounts stack by default, provided both rules are active and their conditions are met. A product with a ten percent Catalog Price Rule plus an additional Cart Price Rule coupon for another ten percent quickly leads to a total discount that was not economically intended. The "Discard subsequent rules" option on a Cart Price Rule prevents further Cart Price Rules from applying afterward, but it has no effect on the Catalog Price Rule discount already baked into the catalog price.

A proven pattern for avoiding conflicts is to configure Cart Price Rules so the calculation basis is explicitly documented: is the coupon discount based on the regular price, or on the price already reduced by the Catalog Price Rule? Magento uses the price valid at the moment of cart calculation by default, which already includes every active Catalog Price Rule. Without this knowledge, promotions frequently produce unexpectedly high discounts that only surface late in reporting.

The sort order of multiple Cart Price Rules determines which rule is evaluated first and whether "Discard subsequent rules" blocks subsequent rules. Catalog Price Rules have their own priority, configured independently of Cart Price Rules. A clean naming scheme and documented prioritization of both rule systems within the team prevents new marketing campaigns from unintentionally colliding with existing Catalog Price Rules.

7. Performance Implications: Reindex Cost with Catalog Rules vs. Runtime Calculation with Cart Rules

Catalog Price Rules shift the compute load onto the indexer run, not onto the page request. That means: the more customer groups, websites and products a Catalog Price Rule affects, the longer the reindex of catalogrule_rule and catalogrule_product takes. On large B2B catalogs with hundreds of customer groups, a single reindex run can take several minutes, especially in "Update on Save" indexer mode, where every save immediately triggers a full reindex.

In "Update by Schedule" indexer mode, the reindex instead runs in the background via the indexer_reindex_all_invalid cron job, which does not block the admin user when saving a rule, but does introduce a delay until the price change becomes visible in the catalog. For projects with frequent price changes, "Update by Schedule" combined with a tightly scheduled cron interval is the right choice to smooth out reindex spikes without delaying visible freshness too much.

Cart Price Rules, in contrast, carry no indexer cost, because no pre-computation ever takes place. Their cost instead occurs on every single cart and checkout request, when the RulesApplier checks every active rule against the current quote. With many simultaneously active Cart Price Rules with complex conditions, checkout response time increases measurably, which is why inactive or expired rules should be consistently disabled rather than merely left to expire on a schedule.


#!/usr/bin/env bash
# Reindex catalog price rules after a rule change or a reduced schedule window
bin/magento indexer:reindex catalogrule_rule
bin/magento indexer:reindex catalogrule_product

# Check indexer mode and pending status for both catalog rule indexers
bin/magento indexer:status catalogrule_rule
bin/magento indexer:status catalogrule_product

# Switch from schedule (cron) to realtime for immediate price updates while testing
bin/magento indexer:set-mode realtime catalogrule_rule catalogrule_product

# Note: Cart Price Rules have no indexer entry at all, there is nothing to reindex here

8. Creating Both Rule Types Programmatically via Service Contracts

Both rule systems can be created programmatically via dedicated Service Contracts, which matters for data migrations, setup scripts, or automated campaign creation. Catalog Price Rules expose \Magento\CatalogRule\Api\CatalogRuleRepositoryInterface, Cart Price Rules expose \Magento\SalesRule\Api\RuleRepositoryInterface. Both repositories follow the same principle: a data object is created via a factory, populated with the relevant properties, and then persisted through the repository's save() method.


<?php

declare(strict_types=1);

namespace Mironsoft\PriceRuleTools\Service;

use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface;
use Magento\CatalogRule\Api\Data\RuleInterfaceFactory;
use Magento\Framework\Exception\LocalizedException;

/**
 * Creates a Catalog Price Rule programmatically via the Service Contract.
 */
class CatalogPriceRuleCreator
{
    /**
     * @param CatalogRuleRepositoryInterface $catalogRuleRepository Repository for persisting catalog rules
     * @param RuleInterfaceFactory $ruleFactory Factory for the catalog rule data object
     */
    public function __construct(
        private readonly CatalogRuleRepositoryInterface $catalogRuleRepository,
        private readonly RuleInterfaceFactory $ruleFactory,
    ) {
    }

    /**
     * Creates a ten percent Catalog Price Rule for a website and all customer groups.
     *
     * @param int $websiteId Website scope for the rule
     * @return int Persisted rule id
     * @throws LocalizedException
     */
    public function createTenPercentRule(int $websiteId): int
    {
        $rule = $this->ruleFactory->create();
        $rule->setName('Summer Sale Catalog Rule');
        $rule->setIsActive(true);
        $rule->setWebsiteIds([$websiteId]);
        $rule->setCustomerGroupIds([0, 1, 2, 3]);
        $rule->setSimpleAction('by_percent');
        $rule->setDiscountAmount(10);
        $rule->setStopRulesProcessing(false);

        $savedRule = $this->catalogRuleRepository->save($rule);

        return (int) $savedRule->getRuleId();
    }
}

The save() method automatically triggers invalidation of the associated indexer for Catalog Price Rules, so a subsequent reindex run, whether manual or via cron, picks up the new rule correctly. For Cart Price Rules this step is entirely absent, because there is no indexing that would ever need to be invalidated.


<?php

declare(strict_types=1);

namespace Mironsoft\PriceRuleTools\Service;

use Magento\SalesRule\Api\RuleRepositoryInterface;
use Magento\SalesRule\Api\Data\RuleInterfaceFactory;
use Magento\Framework\Exception\LocalizedException;

/**
 * Creates a Cart Price Rule with a coupon code programmatically via the Service Contract.
 */
class CartPriceRuleCreator
{
    /**
     * @param RuleRepositoryInterface $ruleRepository Repository for persisting sales rules
     * @param RuleInterfaceFactory $ruleFactory Factory for the sales rule data object
     */
    public function __construct(
        private readonly RuleRepositoryInterface $ruleRepository,
        private readonly RuleInterfaceFactory $ruleFactory,
    ) {
    }

    /**
     * Creates a coupon based Cart Price Rule with a fixed discount amount.
     *
     * @param string $couponCode Coupon code required at checkout
     * @param array<int> $websiteIds Websites the rule is valid for
     * @return int Persisted rule id
     * @throws LocalizedException
     */
    public function createCouponRule(string $couponCode, array $websiteIds): int
    {
        $rule = $this->ruleFactory->create();
        $rule->setName('Newsletter Signup Coupon');
        $rule->setIsActive(true);
        $rule->setCouponType(\Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC);
        $rule->setCouponCode($couponCode);
        $rule->setWebsiteIds($websiteIds);
        $rule->setCustomerGroupIds([0, 1, 2, 3]);
        $rule->setSimpleAction('cart_fixed');
        $rule->setDiscountAmount(15);
        $rule->setStopRulesProcessing(true);

        $savedRule = $this->ruleRepository->save($rule);

        return (int) $savedRule->getRuleId();
    }
}

Both examples use Constructor Property Promotion per PHP 8.4 standard and deliberately avoid direct model instantiation in favor of the Service Contracts. This makes the classes testable, decoupled from the concrete ORM implementation, and compatible with future Magento versions, which can swap out the model layer internally at any time without the interface ever changing.

9. Debugging: Why a Rule Does Not Apply

The most common reason a Catalog Price Rule or Cart Price Rule fails to apply is an incorrectly configured website or customer group assignment. A rule activated only for website 1 has no effect on orders placed through website 2, even if the catalog and customer groups appear identical. Just as common: the customer is browsing as a guest, meaning the customer group "NOT LOGGED IN", while the rule is only activated for registered customer groups.

For Catalog Price Rules, the second most common cause is a stale index. If the indexer runs in "Update by Schedule" mode and the cron job did not run in time, the catalog keeps showing the old price even though the rule is marked active in the admin panel. A manual run of bin/magento indexer:reindex catalogrule_rule catalogrule_product followed by a cache flush resolves most cases immediately.

For Cart Price Rules, the cause often lies in the validity period, the From/To dates, or in the priority of multiple rules: a higher-priority rule with "Discard subsequent rules" prevents a later-evaluated rule from applying at all. A coupon already used up, or an incorrectly configured usage limit per customer, also causes a rule to be technically active while no longer applicable to a specific customer. A look into salesrule_coupon_usage and the rule's condition configuration clarifies most cases within minutes.

10. Summary

Catalog Price Rules and Cart Price Rules solve different problems in Magento 2 and should never be treated as interchangeable tools. Catalog Price Rules pre-compute discounts via an indexer, display them already in the catalog, and support no coupons, but they incur reindex cost on every change. Cart Price Rules compute discounts live at runtime in the cart, support coupons and complex conditions, but cost compute time on every checkout request.

The decision tree is simple: if the discount needs to be visible in the catalog already, a Catalog Price Rule is the right choice. If the action requires a coupon, a minimum order total, or cross-cart conditions, a Cart Price Rule is the right tool. Anyone who deliberately combines both rule systems, documents the order of application, and picks indexer modes that match the pace of change avoids double discounting and unnecessary performance cost in production.

Catalog Price Rules vs. Cart Price Rules: The Essentials at a Glance

Application point

Catalog Price Rules apply already in the catalog, before the cart. Cart Price Rules only apply in the cart and checkout, at runtime.

Indexing

Catalog Price Rules require the catalogrule_rule and catalogrule_product indexers. Cart Price Rules have no index of their own.

Coupons

Only Cart Price Rules support coupon codes, fixed or auto-generated. Catalog Price Rules have no coupon mechanism at all.

Performance

Catalog Price Rules cost reindex time on save. Cart Price Rules cost compute time on every cart and checkout request.

11. FAQ: Catalog Price Rules vs. Cart Price Rules in Magento 2

1Main difference between Catalog Price Rules and Cart Price Rules?
Catalog Price Rules pre-compute via an indexer and show the price already in the catalog. Cart Price Rules compute live in the cart and support coupons.
2Can Catalog Price Rules use coupons?
No. Coupons are a pure Cart Price Rule feature, because Catalog Price Rules are already resolved in the index before any customer interaction.
3Old catalog price after saving a rule?
The catalogrule indexer has not been rebuilt yet. A manual reindex of catalogrule_rule and catalogrule_product usually resolves it right away.
4Do Cart Price Rules cause indexer overhead?
No, Cart Price Rules have no indexer entry and are computed exclusively at runtime.
5Create a Catalog Price Rule programmatically?
Via CatalogRuleRepositoryInterface: create a rule object via factory, set website, customer groups and action, persist via save.
6Create a Cart Price Rule with a coupon programmatically?
Via RuleRepositoryInterface: set coupon type, coupon code, website and customer group ids and action, persist via save.
7What happens when both rule types apply at once?
Discounts stack by default. Discard subsequent rules only blocks further Cart Price Rules, not the Catalog Price Rule discount.
8Recommended indexer mode for Catalog Price Rules?
Update by Schedule for frequent changes with a tight cron interval. Update on Save for small catalogs needing immediate visibility.
9Cart Price Rule with a coupon does not apply, why?
Usually an expired validity period, a reached usage limit per customer, or a higher-priority rule with Discard subsequent rules.
10Catalog Price Rules or Cart Price Rules for B2B special pricing?
Permanent, catalog-visible B2B pricing belongs in Catalog Price Rules. Time-limited or coupon-based B2B promotions belong in Cart Price Rules.

Mironsoft

Magento 2 pricing rules, discount strategy and performance audits

Catalog Price Rules and Cart Price Rules, set up right?

We review existing pricing rules for overlaps, model Catalog Price Rules and Cart Price Rules around your business model, and optimize indexer configuration and checkout performance.

Pricing rule audit

Full review of every Catalog Price Rule and Cart Price Rule for overlaps and double discounting

Indexer tuning

Optimizing indexer modes, cron intervals and reindex strategy for large catalogs and many customer groups

Service Contract migration

Programmatic rule creation via CatalogRuleRepositoryInterface and RuleRepositoryInterface for migrations