Saleor 3.21.0stable Released May 21, 2025
⚠️ Breaking Change Impact Score: 7/10

Saleor 3.21.0 — Release Notes & Engineering Impact

Source available — automated, not human-reviewed

Component: saleor · Channel: stable. Automated analysis, not a compatibility certification. How this works.

Analysis updated: 2026-11-01.

Breaking-change evidence from the source

  • ⚠ This is a breaking change, so if you use the old OpenTracing feature in your custom deployment, you will need to move to an OTEL-capable collector.**
  • 🚨 Breaking changes > **⚠**  Saleor 3.21 includes several breaking changes, such as dropping deprecated or unused plugins and plugin manager methods, changed API behavior - e.g. making certain fields required or altering returned values in specific cases. Below is a list of all the breaking changes. You’ll find a complete list of breaking changes below. For a detailed explanation of each change and guidance on how to adapt your code, refer to the [Upgrade Guide](https://docs.saleor.io/upgrade-guides/core/3-20-to-3-21).
  • ⚠**  Saleor 3.21 includes several breaking changes, such as dropping deprecated or unused plugins and plugin manager methods, changed API behavior - e.g. making certain fields required or altering returned values in specific cases. Below is a list of all the breaking changes. You’ll find a complete list of breaking changes below. For a detailed explanation of each change and guidance on how to adapt your code, refer to the [Upgrade Guide](https://docs.saleor.io/upgrade-guides/core/3-20-to-3-21).
Read the original context ↗
Source flags breaking changes — review applicability
📄 .MD
⚡ Executive Summary (TL;DR)

Saleor 3.21.0. Source highlight: Welcome to the Saleor 3.21 Release! This release is full of exciting updates — including a few major highlights, numerous improvements to the GraphQL API, and dozens of bug fixes. Read on to explore what’s new!

Who Does This Impact? (Role-Based Analysis)

💻 Software Engineers & Devs

Check the source notes against the package and APIs you use before upgrading.

📊 Product & Engineering Leads

Use the documented changes to assess relevance to your product; release frequency alone is not a quality measure.

⚙️ DevOps & Security

Verify the target version, runtime requirements and rollback procedure in the official documentation.

⚠️ Breaking Changes & Deprecations (3)

  • ⚠ This is a breaking change, so if you use the old OpenTracing feature in your custom deployment, you will need to move to an OTEL-capable collector.**
  • 🚨 Breaking changes > **⚠**  Saleor 3.21 includes several breaking changes, such as dropping deprecated or unused plugins and plugin manager methods, changed API behavior - e.g. making certain fields required or altering returned values in specific cases. Below is a list of all the breaking changes. You’ll find a complete list of breaking changes below. For a detailed explanation of each change and guidance on how to adapt your code, refer to the [Upgrade Guide](https://docs.saleor.io/upgrade-guides/core/3-20-to-3-21).
  • ⚠**  Saleor 3.21 includes several breaking changes, such as dropping deprecated or unused plugins and plugin manager methods, changed API behavior - e.g. making certain fields required or altering returned values in specific cases. Below is a list of all the breaking changes. You’ll find a complete list of breaking changes below. For a detailed explanation of each change and guidance on how to adapt your code, refer to the [Upgrade Guide](https://docs.saleor.io/upgrade-guides/core/3-20-to-3-21).

✨ New Features & Capabilities (6)

  • Add `CheckoutCustomerNoteUpdate` mutation.
  • Add the `customerNote` field to the `Checkout` type to make it consistent with the `Order` model.
  • Add filterable subscriptions for checkout events (`checkoutCreated`, `checkoutUpdated`, `checkoutFullyPaid`, `checkoutMetadataUpdated`).
  • Add prior price fields to `VariantPricingInfo`, `ProductPricingInfo`, and `CheckoutLine`.
  • Add the `type` field to the `TaxableObjectDiscount` type.
  • Add the `productVariants` field to `Product` instead of `variants`.

⚡ Performance & Enhancements (3)

  • Drop the `manager.perform_mutation` method.
  • Improve error handling when trying to set invalid metadata. Now, invalid metadata should properly return `error.field` containing `metadata` or `privateMetadata`, instead of generic `input`.
  • Improve status calculation for orders with waiting-for-approval fulfillments.

🔧 Bug Fixes & Patches (6)

  • Fix checkout `line.undiscountedTotalPrice` and `line.undiscountedUnitPrice` calculation.
  • Fix GraphQL explorer not displaying docs if the API is hidden behind a reverse proxy.
  • Fix webhook `PRODUCT_VARIANT_METADATA_UPDATED` not being sent when the `productVariantUpdate` mutation was called. Now, when `metadata` or `privateMetadata` is included in `ProductVariantUpdateInput`, both `PRODUCT_VARIANT_METADATA_UPDATED` and `PRODUCT_VARIANT_UPDATED` will be emitted (if subscribed).
  • Fix the `webhookTrigger` payload type for events related to `ProductVariant`.
  • Fix the undiscounted price taxation in order calculations when the Avatax plugin is used.
  • Fix a potential crash when checkout metadata is accessed with high concurrency.
📄 View Raw Release Notes (Original Developer Log)
Welcome to the Saleor 3.21 Release! This release is full of exciting updates — including a few major highlights, numerous improvements to the GraphQL API, and dozens of bug fixes. Read on to explore what’s new! # 🔦 OpenTelemetry-based Distributed Observability Due to the added complexity of composable architectures, observability and monitoring become critical for diagnosing issues and sometimes even determining if the system is operational. Observing each individual system independently may not provide all the answers, as in practice, there are cases where a solution is not transactional despite all of its individual services reporting “status green.” We recognize this challenge, and this release is our attempt to begin addressing it. Historically, Saleor's observability layer relied on OpenTracing. It was highly detailed in terms of APM-level tracing output, but provided no support for metrics, which had to be calculated on the receiving end of telemetry. It also provided no easy way to correlate data from a large multi-service environment. Saleor 3.21 replaces the old OpenTracing standard with its successor and the current industry standard, OpenTelemetry. It comes with support for metrics, and indeed includes some of the most valuable performance metrics. It also paves the way for adding logs and events, something future releases of Saleor will take advantage of. > **⚠ This is a breaking change, so if you use the old OpenTracing feature in your custom deployment, you will need to move to an OTEL-capable collector.** > We've also split the tracing telemetry stream into data that is generally useful for Saleor users and data that is useful for people working on the codebase. You can now subscribe to the "public" stream of telemetry and get the full picture without all the internal optimization details like individual SQL queries. This is a huge step in cross-stack observability as it enables telemetry data to be streamed in OTLP format to your preferred monitoring solution, like Datadog or New Relic. Thanks to supporting the W3C Trace Context, signals from multiple services hosted by different vendors can be connected to tell the whole story of a page view that originates in your storefront, crosses into Saleor's core (perhaps numerous times due to parallel queries being executed), and then invokes a number of synchronous apps before a response is returned to the storefront for rendering. Telemetry streaming for Saleor Cloud customers is currently in closed beta, so get in touch with our support for details about availability. See the [documentation](https://docs.saleor.io/setup/telemetry) for more information. # 🔂 Sync webhooks circuit breaker This feature may look small on the surface, but it can be a key factor in smoothly handling large events, like promotions during high season. When enabled, the circuit breaker feature temporarily shuts down the app that generates a large number of webhook errors. This prevents the queue from clogging the whole stack and helps keep the store operational, even in high-stress conditions. Before enabling the feature, you can set it up during a dry run, during which you can select which events should be only monitored and which should be blocked if the error threshold is hit. The feature is disabled by default, so no breaking change will be introduced with the upgrade. Find more details in the [documentation](https://docs.saleor.io/developer/extending/webhooks/synchronous-events/circuit-breakers). # 💪 Strong validation of non-GraphQL APIs Beyond the GraphQL API, Saleor includes several other APIs that traditionally lacked built-in validation — unlike GraphQL, which provides it out of the box. These include app manifest exchanges during app installation, custom JSON endpoints or sync webhooks that expect structured responses from apps. In this release, we’re starting to address that gap by introducing strong validation to these APIs. In the first phase, we’re focused on sync webhooks, which are essential for integrating external shipping methods, payment gateways, and tax providers. For these webhooks, we’ve added response validation to ensure correct data types, string length constraints or min/max checks for numeric fields. If validation fails, Saleor will handle it gracefully depending on the webhook type: - For shipping webhooks, log a warning and continue processing without the app response. - For tax and payment webhooks, store errors in the appropriate model fields, such as `Checkout.tax_error`, `Order.tax_error`, or `TransactionEvent.message`, and break the checkout/order processing. Full list of webhooks with support for strong validation: - Shipping webhooks: - `CHECKOUT_FILTER_SHIPPING_METHODS` - `ORDER_FILTER_SHIPPING_METHODS` - `SHIPPING_LIST_METHODS_FOR_CHECKOUT` - Tax webhooks: - `CHECKOUT_CALCULATE_TAXES` - `ORDER_CALCULATE_TAXES` - Payment and transaction webhooks: - `LIST_STORED_PAYMENT_METHODS` - `PAYMENT_GATEWAY_INITIALIZE_SESSION` - `TRANSACTION_INITIALIZE_SESSION` - `TRANSACTION_PROCESS_SESSION` - `TRANSACTION_CHARGE_REQUESTED` - `TRANSACTION_CANCEL_REQUESTED` - `TRANSACTION_REFUND_REQUESTED` Support for additional sync webhooks and custom endpoints is coming soon. In the future, we also plan to publish a static JSON Schema, similar to `schema.graphql` for GraphQL API, to provide a fully documented and tooling-friendly format for these APIs. The best part? Validation is powered by Pydantic, a favorite in the Python community — making it fast, expressive, and easy to work with. # 🛒 Configurable address storing strategy in checkout Saleor 3.21 introduces more flexibility in how customer addresses are saved during checkout handling addresses in checkout. This release adds new `saveBillingAddress` and `saveShippingAddress` flags, giving you fine-grained control over whether shipping or billing addresses should be stored in a customer’s address book. This is how it works: - Flags are only relevant when completing a checkout or draft order for a logged-in user. - By default, Saleor still saves addresses for checkouts but not for draft orders — this behavior remains unchanged unless overridden. - The new flags are now supported in these mutations: `checkoutCreate`, `checkoutShippingAddressUpdate`, `checkoutBillingAddressUpdate`, `draftOrderCreate`, `draftOrderUpdate`. - Flags must be provided together with a valid address input; otherwise, an error is raised. Note that these settings are ignored for Click & Collect delivery methods — shipping addresses are not saved in those cases. See the [documentation](https://docs.saleor.io/developer/order/address#customer-address-saving-strategies) for more details. # 🚨 Breaking changes > **⚠**  Saleor 3.21 includes several breaking changes, such as dropping deprecated or unused plugins and plugin manager methods, changed API behavior - e.g. making certain fields required or altering returned values in specific cases. Below is a list of all the breaking changes. You’ll find a complete list of breaking changes below. For a detailed explanation of each change and guidance on how to adapt your code, refer to the [Upgrade Guide](https://docs.saleor.io/upgrade-guides/core/3-20-to-3-21). > ## Plugins - Drop the invoicing plugin. For an example of a replacement, see https://docs.saleor.io/developer/app-store/apps/invoices. - Drop the deprecated "Stripe (Deprecated)" payment plugin. If your codebase refers to `mirumee.payments.stripe`, you will need to migrate to the supported plugin, `saleor.payments.stripe`. - Drop the `manager.perform_mutation` method. - Drop the `change_user_address` method from the plugin manager. ## GraphQL API - The `accountRegister` mutation no longer returns the `ID` of a user. - The `requestPasswordReset` mutation will not fail if the `channel` was not provided for non-staff users. - The `transactionId` argument of `OrderGrantRefundCreateInput` is now required. - Behavior change: `invoiceRequest` mutation no longer throws an error if only the app with `INVOICE_REQUESTED` webhook is installed (without the invoice plugin). - Security update - the following mutations no longer reveal if an email is registered: `accountRegister`, `confirmAccount`, `requestPasswordReset`, `setPassword`. ## Metadata - Behavior change: `transactionUpdate` now merges input `metadata` and `privateMetadata` with existing values, instead of replacing them. - `MANAGE_ORDERS` permission is now required to update metadata on `Order` and `OrderLine` types. ## Webhooks - `draftOrderUpdate` mutation no longer triggers the `DRAFT_ORDER_UPDATED` webhook if no changes were made. - `orderUpdate` mutation no longer triggers the `ORDER_UPDATED` webhook if no changes were made. - Sync webhooks are no longer triggered during read-only queries. The following GraphQL queries no longer invoke sync webhooks or plugins for tax/shipping: - Checkout queries `checkouts`, `checkoutLines`, `me.checkouts`: - No longer trigger `CHECKOUT_CALCULATE_TAXES` - No longer trigger `SHIPPING_LIST_METHODS_FOR_CHECKOUT` - No longer trigger `CHECKOUT_FILTER_SHIPPING_METHODS` - Orders queries `orders`, `draftOrders`, `me.orders`: - No longer trigger `ORDER_CALCULATE_TAXES` - No longer trigger `ORDER_FILTER_SHIPPING_METHODS` ## Miscellaneous - Google Cloud Platform (GCP): The environment variable for private storage (e.g., webhook event delivery payloads) was renamed from `GS_MEDIA_BUCKET_NAME` to `GS_MEDIA_PRIVATE_BUCKET_NAME`. # GraphQL API - Checkout: - Add `CheckoutCustomerNoteUpdate` mutation. - Add the `customerNote` field to the `Checkout` type to make it consistent with the `Order` model. - Fix checkout `line.undiscountedTotalPrice` and `line.undiscountedUnitPrice` calculation. - Return the normalized price if the checkout prices have not expired; otherwise, fetch the price from the variant channel listing. - The `checkoutShippingAddressUpdate` mutation no longer raises an error when the shipping address is updated for a checkout that does not require shipping. - `CheckoutLinesUpdate` now accepts `metadata` for each line in the input. That means updating checkout lines and their metadata can be done in a single mutation. - `CheckoutLinesAdd` now properly validates `metadata` provided in input. - `CheckoutCreateInput` now accepts `metadata` and `privateMetadata` fields, so `checkoutCreate` can now create a checkout with metadata in a single call. - Add filterable subscriptions for checkout events (`checkoutCreated`, `checkoutUpdated`, `checkoutFullyPaid`, `checkoutMetadataUpdated`). - `checkout.shippingMethods` and `checkout.availableShippingMethods` will no longer return external shipping methods if their currency differs from the checkout's currency. - Orders: - Mutation `draftOrderCreate` and `draftOrderUpdate` now support adding `metadata` and `privateMetadata` (via `DraftOrderCreateInput`). - `orderUpdate` mutation now allows updating `metadata` and `privateMetadata` via `OrderUpdateInput`. - `DraftOrderInput`, `OrderUpdateInput` and `DraftOrderCreateInput` now allow to provide `languageCode`. - Products: - Add prior price fields to `VariantPricingInfo`, `ProductPricingInfo`, and `CheckoutLine`. - Add the `type` field to the `TaxableObjectDiscount` type. - Add the `productVariants` field to `Product` instead of `variants`. - Add slugs to product/category/collection/page translations. Allow to query by translated slug. - Discounts: - `GiftCardCreate` and `GiftCardUpdate` mutations now allow setting `metadata` and `privateMetadata` fields via `GiftCardCreateInput` and `GiftCardUpdateInput`. - Expose line-level discounts through the `OrderLineDiscount` type, retrievable via the `OrderLine.discounts` field. - Introduce total field in `OrderDiscount` to replace the amount field. - Introduce the `useLegacyLineVoucherPropagation` flag to control legacy propagation behavior for specific voucher types. - Metadata: - Improve error handling when trying to set invalid metadata. Now, invalid metadata should properly return `error.field` containing `metadata` or `privateMetadata`, instead of generic `input`. - Deprecations: - Deprecate `draftOrderInput.discount` field. - Deprecate `OrderDiscount.amount` field. - Deprecate `Product.variants` field. - Other: - Added a warning to metadata input fields in the GraphQL schema informing users never to store sensitive data. This ensures user awareness of potential security policy violations and compliance risks of storing certain types of data. - Fix GraphQL explorer not displaying docs if the API is hidden behind a reverse proxy. # Webhooks - Webhooks `CHECKOUT_FILTER_SHIPPING_METHODS` & `ORDER_FILTER_SHIPPING_METHODS` are no longer executed if no changes were made in the related mutations (no available shipping methods, e.g. due to lack of shipping address). - Fix webhook `PRODUCT_VARIANT_METADATA_UPDATED` not being sent when the `productVariantUpdate` mutation was called. Now, when `metadata` or `privateMetadata` is included in `ProductVariantUpdateInput`, both `PRODUCT_VARIANT_METADATA_UPDATED` and `PRODUCT_VARIANT_UPDATED` will be emitted (if subscribed). - Updating draft order’s shipping method via `orderUpdateShipping` mutation will now emit `DRAFT_ORDER_UPDATED` webhook, instead of `ORDER_UPDATED`. - Updating an editable order’s shipping method via the `orderUpdateShipping` mutation will now emit an `ORDER_UPDATED` webhook when the `shippingMethod` is cleared (by passing `null` in the input). - Webhook `TRANSACTION_CANCELATION_REQUESTED` now provides a decimal value in the `action.amount` field instead of null. - The `TransactionAction.amount` field in subscription payload for webhooks `TransactionRefundRequested`, `TransactionChargeRequested`, `TransactionCancelationRequested` is now a non-null field. - Field `amount` in the response for synchronous webhooks `TRANSACTION_CHARGE_REQUESTED`, `TRANSACTION_REFUND_REQUESTED`, `TRANSACTION_CANCELATION_REQUESTED`, `TRANSACTION_INITIALIZE_SESSION`, `TRANSACTION_PROCESS_SESSION` is now an optional field. If omitted, Saleor will default to using the `action.amount` specified in the payload. - Fix the `webhookTrigger` payload type for events related to `ProductVariant`. - Truncate lengthy responses in `EventDeliveryAttempt` objects. # Miscellaneous - Add support for numeric and lower-case boolean environment variables. - Fix the undiscounted price taxation in order calculations when the Avatax plugin is used. - Fix a potential crash when checkout metadata is accessed with high concurrency. - Fix a crash when the decimal scalar is passed a non-normal value. - Add validation for tax data received from the tax app. - Fix order-level discounts handling when using an app for tax calculation. - Fix bug when manual line discount doesn't override line-level vouchers. - Drop tax data line number validation for the Avatax plugin. - Fix decreasing voucher code usage after changing `includeDraftOrderInVoucherUsage` to false. - Fix the undiscounted price taxation when prices are entered with taxes. - Fix `products` sorting when using `sortBy: {field: COLLECTION}`. - Fix checkout funds releasing task. - Use denormalized base prices during order update. - `UNCONFIRMED` orders will never refresh their base prices, - `DRAFT` orders will refresh their base prices after a default of 24 hours. - Fix bug which, in some cases, caused product name translations to be empty in order lines. - Improve status calculation for orders with waiting-for-approval fulfillments. - Fix incorrect gift card balances after covering the full order total. - Fix tax class not clearing when selecting a shipping method without a tax class. - The prices for draft orders created in `OrderBulkCreate` are now correctly calculated. - Fix incorrect stock deallocation when multiple order lines share the same ProductVariant. - Decrease allocations for lines with inventory tracking disabled, if allocations exist. - Allow to change admin email plugin custom templates back to default. - Add support for the `AWS_S3_URL_PROTOCOL` environment variable. - Fix Celery worker issues when using SQS by using `celery[sqs]` extras instead of a direct `pycurl` dependency. - Fix saving webhook payload to Azure Storage. - Make the token generator class configurable. - Add [an alg](https://datatracker.ietf.org/doc/html/rfc7517#section-4.4) key to JWKS available at `/.well-known/jwks.json`. - Fix the healthcheck middleware (`/health/` endpoint) not forwarding incoming traffic whenever the protocol wasn't HTTP (such as WebSocket or Lifespan). - Remove support for the `django-debug-toolbar` debugging tool and the `ENABLE_DEBUG_TOOLBAR` env variable. # 🫶 Thank You A total of 21 people contributed to the latest Saleor 3.21 release 🎉. Among them, 5 were open-source contributors 🌟: - `@p-febis` - `@jqob` - `@bnkwines` - `@pitkes22` - `@delemeator` Big kudos to these OS contributors for their valuable efforts and community-driven support that helped make this release possible 🙌. If you want to know where Saleor is heading next, make sure to visit [https://saleor.io/roadmap](https://saleor.io/roadmap) 🗾. You don’t want to miss [Community Updates](https://saleor.io/community-update) and all the exciting discussions and initiatives in our Discord server. See you on [https://saleor.io/discord](https://saleor.io/community-update) 💬!