---
title: Free shipping
related:
  - https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/catalog-discounts.md
  - https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/order-discounts.md
  - https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/coupon-codes.md
  - https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/customer-eligibility-customization.md
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

> **License:** Advanced license required.
>
> Features described on this page require the Xperience by Kentico **Advanced** license tier.

The free shipping promotion framework allows you to create custom promotion rules that grant free shipping during [price calculation](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/price-calculation.md). Unlike [catalog](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/catalog-discounts.md) and [order discounts](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/order-discounts.md), which reduce a price, free shipping eliminates the shipping cost entirely when the cart meets the rule's conditions. Free shipping always removes the shipping price in full, partial discounts are not possible.

Use free shipping to implement:

- A minimum amount or quantity that unlocks free shipping (e.g., "Spend $50, get free shipping")
- Free shipping earned by specific products or categories
- Free shipping limited to selected shipping methods

> **Info:** **Free shipping doesn't compete with other promotion types**
>
> A customer can receive free shipping together with any applicable catalog and order discounts.

## General promotion capabilities

Free shipping promotions share the same configuration and evaluation framework as [catalog](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/catalog-discounts.md) and [order discounts](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/order-discounts.md), so the following capabilities work identically:

- [Coupon codes](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/coupon-codes.md) – Optionally require a discount code for redemption.
- [Customer eligibility](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/customer-eligibility-customization.md) – Restrict the promotion to all visitors, registered members only, or a custom eligibility rule.
- [Activation scheduling](https://docs.kentico.com/documentation/business-users/manage-commerce-stores.md#create-a-promotion) – Set start and end dates for time-limited offers.

## Free shipping promotion rule overview

A **promotion rule** defines the logic for determining whether an order qualifies for free shipping. You implement promotion rules as classes that inherit from `FreeShippingPromotionRule`.

The rule receives the calculation data for the whole cart or order – the cart items with their prices after other discounts, the [calculation mode](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/price-calculation.md#calculation-modes) (shopping cart or checkout), and, in checkout, the shipping method the customer selected. Based on this data, the rule answers a single question: does the order qualify for free shipping? If it does, the rule can also name the specific products that earned the reward. In the shopping cart mode, the shipping method is not selected yet and the rule checks whether free shipping is available for any free shipping method.

### Promotion candidate

A **promotion candidate** represents a reward that a free shipping rule grants to an order. When a promotion rule determines that the order qualifies, it returns a `FreeShippingPromotionCandidate` containing:

- `TriggerProductIdentifiers` – An optional list of the products that caused the cart to qualify. Leave this empty for a whole-cart condition, such as a minimum order amount. See [Restrict the reward to specific products](#restrict-the-reward-to-specific-products).

When the cart doesn't qualify, return `null` instead of a candidate.

> **Info:** Unlike `CatalogPromotionCandidate` and `OrderPromotionCandidate`, `FreeShippingPromotionCandidate` doesn't carry a discount amount – the reward is always the full shipping price.

### Promotion rule properties

Promotion rules can define configurable properties that store managers set when creating promotions in the administration interface. Properties are defined in a separate class implementing `IPromotionRuleProperties` and are decorated with [editing components](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/editing-components.md) to generate the UI. More details are provided in [Define promotion rule properties](#define-promotion-rule-properties).

![Free shipping promotion class hierarchy](https://docs.kentico.com/docsassets/documentation/free-shipping/free-shipping-hierarchy.drawio.svg "Free shipping promotion class hierarchy")

### Promotion selection logic

When multiple free shipping promotions qualify for the same cart, the system selects one to credit: a promotion activated by the customer's coupon code wins if any exists, otherwise the newest qualifying promotion is used. Every qualifying promotion is still evaluated and returned as a candidate, but only the selected one is marked `Applied`. The reward for free shipping promotions is always the full shipping price. Only the selected promotion is persisted when the order is created.

## Create free shipping promotion rules

Implementing a free shipping promotion rule involves creating two components:

1. **Properties class** – Defines configurable settings that store managers can adjust in the administration interface (e.g., minimum order amount, eligible shipping methods). The properties class implements `IPromotionRuleProperties` or inherits from `FreeShippingPromotionRuleProperties`.
2. **Logic class** – Contains the rule's evaluation logic to determine if the cart qualifies for free shipping. The logic class inherits from `FreeShippingPromotionRuleBase` or `FreeShippingPromotionRule`.

These two components work together: the properties class stores the configuration, and the logic class uses those configured values to evaluate the cart during price calculation.

> **Tip:** **Sample implementation**
>
> See **DancingGoatFreeShippingPromotionRule.cs** in the Dancing Goat [project template](https://docs.kentico.com/documentation/developers-and-admins/installation.md#available-project-templates) for a sample rule implementation.

### Define promotion rule properties

The system provides `FreeShippingPromotionRuleProperties` as a base class with common properties already defined and configurable via the administration interface:

- **MinimumRequirementValueType** – Radio group to select no minimum, minimum purchase amount, minimum quantity of items, or minimum single item price
- **MinimumRequirementValue** – Decimal input for the minimum value (visible when a minimum requirement other than "none" is selected)
- **ShippingMethodScope** – Radio group to select whether the reward covers every shipping method or only selected ones
- **ShippingMethods** – Selector for the specific shipping methods the reward covers (visible when the scope is set to specific methods)

You can inherit from this base class to include these standard fields and add your own custom properties. Use [editing component](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/editing-components.md) annotations to generate the input fields for each property:

```csharp title="Properties extending the base properties class"
using System.Collections.Generic;

using CMS.ContentEngine;

using Kentico.Xperience.Admin.Base.FormAnnotations;
using Kentico.Xperience.Admin.DigitalCommerce;

namespace Codesamples.Commerce;

public class ProductFreeShippingProperties : FreeShippingPromotionRuleProperties
{
    // Base class properties include: MinimumRequirementValueType, MinimumRequirementValue,
    // ShippingMethodScope, ShippingMethods

    // Add custom properties for your business logic
    // For example, a property allowing editors to select products that will count toward the minimum requirement
    [ContentItemSelectorComponent(
        typeof(ProductSchemaFilter),
        Label = "Eligible products",
        Order = 1)]
    // Not visible when no minimum requirements are needed to qualify for the shipping
    [VisibleIfNotEqualTo(nameof(MinimumRequirementValueType), "none")]
    public IEnumerable<ContentItemReference> EligibleProducts { get; set; } = [];
}
```

This example uses `ProductSchemaFilter` (an implementation of `IReusableFieldSchemasFilter`) to scope the items offered in the selector, so that only content items that are products in the store appear. For other ways to scope a content item selector, see [Reference - Admin UI form components](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/reference-admin-ui-form-components.md#ciselectorreusable).

**See the 'ProductSchemaFilter' implementation**

```csharp
using System.Collections.Generic;

using Kentico.Xperience.Admin.Base.FormAnnotations;

public class ProductSchemaFilter : IReusableFieldSchemasFilter
{
    IEnumerable<string> IReusableFieldSchemasFilter.AllowedSchemaNames => new List<string> { IProductFields.REUSABLE_FIELD_SCHEMA_NAME };
}
```

> **Info:** The base class properties use negative `Order` values (e.g., -190, -90, -80) to ensure they appear at the top of the form, grouped under their own categories. Keep this in mind when positioning custom properties.

Alternatively, you can directly implement `IPromotionRuleProperties`, bypassing the inheritance entirely, if you need full control over all configuration properties used by the rule.

### Implement the promotion rule

The framework provides two base classes for free shipping promotion rules:

- **FreeShippingPromotionRule** – Use when your properties class inherits from `FreeShippingPromotionRuleProperties`. Provides built-in helper methods that work with the base properties, including minimum requirement evaluation (whole-order minimum, minimum number of items, minimum single item price) and shipping method scoping.
- **FreeShippingPromotionRuleBase** – Use when you need fully custom properties implementing `IPromotionRuleProperties` directly. You must implement all eligibility logic yourself.

Both base classes use the same generic type parameters:

1. `TPromotionRuleProperties` – The rule's [properties class](#define-promotion-rule-properties). Must inherit from `FreeShippingPromotionRuleProperties` for `FreeShippingPromotionRule`, or implement `IPromotionRuleProperties` for `FreeShippingPromotionRuleBase`.
2. `TPriceCalculationRequest` – The price calculation request type.
3. `TPriceCalculationResult` – The price calculation result type.

For rule registration, see [Register the promotion rule](#register-the-promotion-rule).

When your properties class inherits from `FreeShippingPromotionRuleProperties`, `FreeShippingPromotionRule` provides you access to the following built-in methods and features:

- `MeetsMinimumRequirement(items)` – Evaluates the given cart items against `MinimumRequirementValueType` and `MinimumRequirementValue` from the base properties, which can be configured by store managers in the administration. Price and quantity requirements count the item price after all discounts, since the free shipping step runs after order promotions. See [Define promotion rule properties](#define-promotion-rule-properties) for the breakdown of what the `MinimumRequirementValueType` property can represent.
- `GetItemsMeetingMinimumRequirement(items)` – Returns the items that earned the reward. For a minimum single item price requirement, only the items whose own price meets the threshold; for the other requirement types (none, minimum purchase amount, minimum quantity), the whole given set, since those are evaluated on the total across all items rather than any one item. Useful for building `TriggerProductIdentifiers` in the [promotion candidate](#promotion-candidate).
- `IsApplicable` with automatic shipping method scoping – `FreeShippingPromotionRule` overrides `IsApplicable` so the promotion is evaluated only when the shipping method selected in the order is covered by the promotion, as determined by `ShippingMethodScope` and `ShippingMethods` from the base properties. In the shopping cart mode no shipping method is selected yet, so the check passes and the evaluation always runs. See [Filter promotion applicability](#filter-promotion-applicability) if you need additional conditions – call `base.IsApplicable` from your override to keep the shipping method check.
- `GetDiscountValueLabel()` – Already overridden to return a localized "Free shipping" label for display. Override it again only if you need custom wording.

The following sample demonstrates a minimal rule that uses the base properties:

```csharp title="Free shipping promotion rule"
using System;
using System.Collections.Generic;
using System.Linq;

using CMS.Commerce;

using Kentico.Xperience.Admin.DigitalCommerce;

namespace Codesamples.Commerce;

public class MinimumOrderFreeShippingRule
    : FreeShippingPromotionRule<FreeShippingPromotionRuleProperties,
        PriceCalculationRequest, PriceCalculationResult>
{
    public const string IDENTIFIER = "Acme.MinimumOrderFreeShipping";

    public override FreeShippingPromotionCandidate GetPromotionCandidate(
        IPriceCalculationData<PriceCalculationRequest, PriceCalculationResult> calculationData)
    {
        if (!MeetsMinimumRequirement(calculationData.Result.Items))
        {
            // The cart doesn't qualify -- no reward
            return null;
        }

        // For a minimum item price requirement, the qualifying items are trigger products.
        // For the other requirement types, the whole cart met the requirement together, so
        // leaving TriggerProductIdentifiers empty stores the reward once at the order level.
        List<ProductIdentifier> triggerProducts;
        if (Properties.MinimumRequirementValueType.ToString().Equals("ItemPrice", StringComparison.OrdinalIgnoreCase))
        {
            triggerProducts = GetItemsMeetingMinimumRequirement(calculationData.Result.Items)
                .Select(item => item.ProductIdentifier)
                .ToList();
        }
        else
        {
            triggerProducts = [];
        }

        return new FreeShippingPromotionCandidate
        {
            TriggerProductIdentifiers = triggerProducts
        };
    }
}
```

### Register the promotion rule

Register the rule using `RegisterPromotionRuleAttribute` with the following parameters:

1. A unique string identifier for the rule.
2. `PromotionType.Shipping` to indicate this is a free shipping promotion.
3. A display name shown in the administration interface.

Without registration, the rule doesn't appear in the administration interface.

```csharp title="Register a free shipping promotion rule"
[assembly: RegisterPromotionRule<MinimumOrderFreeShippingRule>(
    MinimumOrderFreeShippingRule.IDENTIFIER,
    PromotionType.Shipping,
    "Minimum order -- free shipping")]
```

### Filter promotion applicability

Override the `IsApplicable` method to add high-level conditions that determine whether the promotion should be evaluated at all. This method runs once per promotion before `GetPromotionCandidate`, making it useful for performance optimization when you can skip the entire promotion early.

> **Note:** **Built-in shipping method validation**
>
> When using `FreeShippingPromotionRule`, the base class `IsApplicable` implementation automatically validates the shipping method scope based on `ShippingMethodScope` and `ShippingMethods` properties. Unlike `OrderPromotionRule`, it does **not** validate the minimum purchase requirement – call `MeetsMinimumRequirement` explicitly inside `GetPromotionCandidate` instead, as shown above. You only need to override `IsApplicable` if you have additional custom conditions.

The evaluation pipeline works as follows:

1. **Get active promotions** – The system retrieves all active free shipping promotions.
2. **Check IsApplicable** – For each promotion, `IsApplicable` is called once. If it returns `false`, the promotion is skipped entirely.
3. **Calculate the reward** – For promotions that pass, `GetPromotionCandidate` is called to determine if the cart qualifies.
4. **Select and grant free shipping** – Every promotion for which `GetPromotionCandidate` returns a candidate qualifies. The system then selects one of the qualifying candidates to credit (see [Promotion selection logic](#promotion-selection-logic)).

Use `IsApplicable` for checks that apply to the entire promotion context. For example, to restrict a promotion to registered members while keeping the built-in shipping method validation:

```csharp title="Restrict promotion to registered members"
using System.Threading;
using System.Threading.Tasks;

using CMS.Commerce;

// ...

public override async Task<bool> IsApplicable(
    IPriceCalculationData<PriceCalculationRequest, PriceCalculationResult> calculationData,
    CancellationToken cancellationToken)
{
    BuyerIdentifier buyerIdentifier = calculationData.Request.BuyerIdentifier;

    // Checks if the buyer is a registered member
    if (buyerIdentifier == null || buyerIdentifier.Kind != BuyerIdentifierKind.Member)
    {
        return false;
    }

    // Keeps the automatic shipping method check
    return await base.IsApplicable(calculationData, cancellationToken);
}
```

### Restrict the reward to specific products

A free shipping promotion doesn't have to depend on the whole cart. To grant free shipping only when the cart contains specific products or products from specific categories, first add a property that lets store managers select the eligible products or categories, such as `EligibleProducts` in the [Define promotion rule properties](#define-promotion-rule-properties) example.

`EligibleProducts` holds GUID-identified content item references, while cart items are keyed by the int-identified `ProductIdentifier` – the two can't be compared directly. Match on product data instead, extended with the product's content item GUID – see [Customize price calculation](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/price-calculation/customization.md#extend-data-transfer-objects):

```csharp title="Extend product data with a content item GUID"
using System;

using CMS.Commerce;

public record ProductDataWithContentItemGuid : ProductData
{
    public Guid ContentItemGuid { get; init; }
}
```

Then evaluate the scoped cart items in `GetPromotionCandidate` and return the matching products as `TriggerProductIdentifiers`:

```csharp title="Free shipping rule scoped to eligible products"
using System.Linq;

// ...

public override FreeShippingPromotionCandidate GetPromotionCandidate(
    IPriceCalculationData<PriceCalculationRequest, PriceCalculationResult> calculationData)
{
    var scopeItems = calculationData.Result.Items
        .Where(item => item.ProductData is ProductDataWithContentItemGuid product
            && Properties.EligibleProducts.Any(eligible => eligible.Identifier == product.ContentItemGuid));

    if (!MeetsMinimumRequirement(scopeItems))
    {
        return null;
    }

    return new FreeShippingPromotionCandidate
    {
        TriggerProductIdentifiers = GetItemsMeetingMinimumRequirement(scopeItems)
            .Select(item => item.ProductIdentifier)
            .ToList()
    };
}
```

Recording trigger products matters beyond eligibility – when the order is created, the system stores the granted free shipping reward once per trigger item instead of once for the whole order. This lets a later return or refund of a trigger item identify the free shipping that the item earned. Leave `TriggerProductIdentifiers` empty for whole-cart conditions (such as a minimum order amount that isn't scoped to specific products), and the reward is stored once at the order level instead.

> **Tip:** **Sample implementation**
>
> See **DancingGoatFreeShippingPromotionRule.cs** and **DancingGoatFreeShippingProperties.cs** in the Dancing Goat [project template](https://docs.kentico.com/documentation/developers-and-admins/installation.md#available-project-templates) for a real implementation that scopes the reward by category, product, or tag.

## Access promotion data in calculation results

After price calculation, you can access promotion information from the result to display eligibility to customers or for reporting purposes.

Granted free shipping promotion candidates are stored in the `PromotionData.FreeShippingPromotionCandidates` collection on the `IPriceCalculationResult` returned by price calculation. Each candidate is wrapped in `IPriceCalculationPromotionCandidate<FreeShippingPromotionCandidate>`, exposing the promotion ID, the coupon code that activated it (if any), and the candidate itself:

```csharp title="Access free shipping promotion data"
var qualifiesForFreeShipping = calculationResult.PromotionData.FreeShippingPromotionCandidates.Count > 0;

foreach (IPriceCalculationPromotionCandidate<FreeShippingPromotionCandidate> candidate in calculationResult.PromotionData.FreeShippingPromotionCandidates)
{
    var promotionId = candidate.PromotionID;
    var triggerProducts = candidate.PromotionCandidate.TriggerProductIdentifiers;
    var couponCode = candidate.CouponCode;
}
```
