Cart and order persistence

Product Add-On selections stay attached to their exact WooCommerce cart line. This guide is for store owners who need to understand quantity changes, restored sessions, completed orders, and Order again, and for developers using the WooCommerce Store API.

Before you begin

  • Enable Product Add-Ons under Settings → POM Tweaks → E-Commerce → Products.
  • Create and assign at least one enabled group.
  • Test the complete route from product page to cart and checkout on a staging site before assigning required fields across a live catalog.

Cart and session behavior

Selections with different values create distinct cart configurations. Refreshing the page or returning with the same WooCommerce session restores each accepted value and recalculates its price from the current product, group, field, and reusable-choice configuration.

Long-text fields with Rich text editor enabled retain their permitted HTML in the cart, reopened product form, and order-line metadata. Formatting is filtered again when these values are restored or displayed. Other text fields continue to store plain text. See Let customers enter formatted text to enable the editor.

When a guest signs in, WooCommerce migrates the cart to the customer session and POM transfers ownership only for private uploads referenced by that exact cart. The same configured line can therefore continue to checkout without exposing the file to another browser or account.

Quantity changes preserve the configured price type:

  • A flat fee is charged once for the complete cart line.
  • A quantity-based amount is charged for every unit.
  • A percentage-based amount is recalculated from the current base unit price for every unit.

The accepted cart snapshot stores canonical decimal amounts rather than unbounded binary float results. WooCommerce receives prices normalized at its internal calculation precision, then applies its normal coupon and tax rules. Completed order line totals use the active currency decimals, while the compatibility metadata and saved POM snapshot retain normalized calculation values for REST, webhook, subscription, booking, and historical display consistency.

If a saved option has been removed, hidden, or centrally disabled, WooCommerce displays an error and prevents checkout. The customer must remove that cart line and configure the product again. This avoids silently purchasing an unavailable configuration.

Edit an existing cart item

Select the product name for a cart line with add-ons to reopen its product form. POM restores the accepted values and quantity from the current WooCommerce session. Existing uploads are shown through an authorized download link and keep control; the browser file input remains empty. Select Cancel editing to return to the cart without changing anything, or select Update cart item to save the complete replacement.

An edit replaces the original line instead of adding another one. POM validates the current product or variation, required fields, option availability, uploads, stock, quantity, and server-calculated prices before changing the cart. If any check fails, the original line and its existing file remain in place and the submitted non-file values remain available on the product form for correction. A successfully replaced upload is removed only after the replacement line is accepted.

Cart edit links contain a short-lived opaque token signed for the current site, WooCommerce session, line, and line revision. They never expose or accept a raw cart item key. A token copied to another browser or subsite is rejected. If the line was removed or changed in another tab, the earlier edit is rejected with a stale-item message; reload the cart and start again. Even when the new configuration matches another cart line, POM keeps the edited result as a separate line so quantity is never merged unexpectedly.

Tokens expire after 30 minutes by default. Developers may adjust that lifetime in seconds with pom_tweaks_product_addons_cart_edit_token_ttl; POM enforces a five-minute minimum so a customization cannot make ordinary product-form edits expire immediately.

Completed orders and Order again

A completed order keeps the customer-facing field names and accepted values that applied when it was placed. Later label, availability, or price edits do not rewrite that historical record.

Order again is a new purchase, so it uses current rules instead:

  1. WooCommerce reads the earlier selections.
  2. POM Tweaks matches them to the current product fields and options.
  3. Current labels, restrictions, availability, and prices are applied.
  4. If any earlier selection is no longer valid, the product is not added and the customer is asked to configure it again.

Subscription renewals keep the renewal information supplied by the subscription system instead of rebuilding it as an ordinary repeat order. POM Tweaks restores its saved snapshot and compatibility rows into the renewal cart for REST and Blocks consumers, but it does not revalidate the selection against the current form or replace the grandfathered renewal price. The subscription extension copies the historical visible metadata, _pao_ids, _pao_total, and POM snapshot to the new renewal line without duplicate rows. This rule also applies when paying a failed or pending renewal order.

With Country pricing zones, that historical renewal snapshot also keeps the subscription's stored country and zone. A switch resolves the target product and add-ons from their current definitions in the subscription's stored zone, so proration combines a historical source with a current target in one zone. Resubscribe remains a new purchase and resolves current add-on definitions from the customer's current confirmed country.

The related flows intentionally use different precedence:

  • Order again creates a new purchase and resolves current fields, availability, labels, product price, and add-on prices.
  • A delayed initial payment follows the official Product Add-Ons behavior and reconstructs add-ons from current definitions because Subscriptions does not normally grandfather that line price.
  • A renewal or renewal pay-for-order flow keeps the historical selection and line price supplied by Subscriptions.
  • A customer-initiated switch validates the add-on form for the target product and stores the newly accepted configuration with the switch line.

Resubscribe and prorated switch calculations remain owned by WooCommerce Subscriptions. When an add-on uses an explicit initial-only, recurring, or one-time purchase policy, POM stores that policy and its applied purchase phase in the accepted snapshot; existing add-ons continue to use the all-purchases behavior.

The Product Add-Ons cart and order snapshot includes the applied zone ID, context revision, pricing revision, recurrence phase, normalized policy, and resolved contribution. It does not store or expose unrelated zones. Classic checkout, Cart and Checkout Blocks, renewal orders, order REST, and webhooks retain the same accepted evidence.

For a current purchase, changing the WooCommerce customer country rebuilds the accepted selection in the exact new cart-line zone before totals are applied. POM revalidates current fields and options, applies regional fixed values and tiers, keeps percentages on the regional product or Composite component base, and checks customer-entered custom prices against the new regional minimum and maximum. Transitional rows created through the official WooCommerce Product Add-Ons cart contract are matched by their stable field identity and one unambiguous current option before they are repriced. A removed, reordered, duplicated, or newly invalid selection fails closed and blocks checkout instead of retaining a stale amount. Historical renewal rows deliberately bypass this current-catalog rebuild.

Configure add-ons on an order line

Administrators can configure a product immediately after adding it to an editable order, or revise the selections on an existing line:

  1. Open WooCommerce → Orders and open an order that WooCommerce still permits you to edit.
  2. Add a simple product or variation manually if the line does not exist yet.
  3. Select Configure add-ons below a new line, or Edit add-ons below a configured line.
  4. Complete the current fields. Unavailable choices remain visible as disabled options so their status is clear.
  5. Review Historical selections. Leave a row selected to preserve it, or clear it deliberately when it should no longer belong to the line.
  6. Select Done. POM Tweaks validates the current product definition on the server and refreshes the order items, taxes, totals, and notes.

The browser submits identifiers and customer-entered values only. Labels, availability, field rules, and prices are resolved from the server; changing a browser value cannot set an add-on price. Existing uploads appear through expiring authorized links and are never inserted into a file input. Upload a new file to replace one, keep its row under Saved selections to retain it, or clear the row explicitly. A required upload becomes mandatory only after its saved file is cleared.

When a saved field or option no longer exists, is unavailable, or no longer matches its saved label or pricing definition, its stored snapshot remains the historical authority. Saving the form preserves each checked historical row with its original label, value, and price. A current selection for a single-choice field replaces its obsolete saved selection; unavailable checkbox selections can coexist with new available selections until an administrator clears them. Selecting the current version of the same checkbox option replaces its historical version instead of duplicating it. Unrelated order-item metadata is not removed.

WooCommerce decides whether an order is editable. Paid or otherwise locked orders do not expose the control unless WooCommerce permits editing. The editor works with HPOS and classic order storage, retains any existing per-line discount, recalculates taxes from the order address, and then recalculates order totals. Subscription order lines, subscription variations, and ordinary variations use the same flow. Bundle and Composite integrations can opt compatible container lines into the editor through pom_tweaks_product_addons_display_order_item_editor; the filter receives the default decision, order item, product, order, and resolved public field model, without depending on POM Theme.

Very old extension lines that contain _pao_ids but no stored _pao_total are not editable because their previous price contribution cannot be separated safely from the product price. Their visible historical metadata remains unchanged.

Store API add-to-cart requests

Custom Store API clients can submit add-on values in the pom_product_addons JSON property. Keys are the stable group or product context UUID and field UUID present in the product form markup. Option fields use option UUIDs; checkbox fields use an array of option UUIDs.

{
  "id": 123,
  "quantity": 2,
  "pom_product_addons": {
    "context-uuid": {
      "single-choice-field-uuid": "option-uuid",
      "checkbox-field-uuid": ["first-option-uuid", "second-option-uuid"],
      "text-field-uuid": "Customer value"
    }
  }
}

The server resolves the submitted identifiers against the current definitions. Unknown fields, unknown or unavailable options, invalid values, and omitted required fields return a Store API error. File bodies are not accepted in this JSON contract; file add-ons must be uploaded through the product form.

A public field with type: "custom_textarea" and rich_text: true accepts a string containing basic HTML, for example "<p><strong>Project scope</strong></p><ul><li>Design</li></ul>". The same string contract applies to the corresponding addons_configuration value. The server selects rich-text behavior from the published field definition, filters the HTML, and validates its readable text; a client cannot enable HTML on a plain-text field by submitting its own flag. Custom clients provide their own editing control. Standard WooCommerce displays may simplify formatting according to their own output rules.

Clients already built for WooCommerce Product Add-Ons can continue using addons_configuration. Its keys are the official numeric add-on IDs returned by the compatibility product and group APIs. A multiple-choice value is its zero-based option index, and a checkbox value is an array of zero-based option indices.

{
  "id": 123,
  "quantity": 2,
  "addons_configuration": {
    "1700000001": 0,
    "1700000002": [0, 2],
    "1700000003": "Customer value"
  }
}

File fields accept only an opaque file_id created for the same WooCommerce session by the POM multipart upload route. Arbitrary HTTP URLs, filesystem paths, labels, MIME claims, and prices are not accepted. The temporary file is claimed only after WooCommerce accepts the complete cart line.

For a headless cart, send the same valid Cart-Token header to the multipart upload route and the later Store API request. POM uses that token's cart identity rather than an unrelated browser cookie session, so the opaque file identifier cannot cross into another tokenized cart.

Cart-item responses expose has_addons, flat_fee_total, charge_context, economic_result, edit_token, and edit_url under the pom-tweaks-product-addons extension namespace. economic_result identifies the currency, inherited product tax status and class, and the line contribution protected from coupons. Customer-facing selections also remain available through WooCommerce cart item data. The edit token is opaque, session-bound, revision-bound, and short-lived.

Headless clients edit a line through WooCommerce's native cart extension endpoint. POST to /wp-json/wc/store/v1/cart/extensions with namespace pom-tweaks-product-addons-edit-item. The data object accepts edit_token, product or variation id, optional quantity and variation, and either pom_product_addons or addons_configuration. Existing file values use their same-session opaque file_id. A successful request returns the normal complete Store API cart response; a failed or stale request leaves the original line unchanged.

{
  "namespace": "pom-tweaks-product-addons-edit-item",
  "data": {
    "edit_token": "opaque-token-from-the-cart-item-extension",
    "id": 123,
    "quantity": 2,
    "pom_product_addons": {
      "context-uuid": {
        "single-choice-field-uuid": "replacement-option-uuid"
      }
    }
  }
}

For compatibility, the same cart-item response exposes extensions.addons.addons_data.addons_flat_fees_sum when a flat fee exists. This value is the flat-fee contribution distributed into the current unit price, matching the official extension's response contract.

Store API product responses expose the public field model in the same extension namespace. In Cart and Checkout Block responses, a one-time flat fee remains charged once for the line and WooCommerce distributes it across the current quantity in the presented unit price, as the official extension does. Product permalinks include the signed edit token so classic carts and Cart or Checkout Blocks open the same canonical edit form without trusting values or prices from the URL.

Order API and webhook persistence

Each accepted add-on is written as ordinary visible WooCommerce line-item metadata, so native order REST responses, order webhooks, administration screens, and emails continue to receive the familiar add-on name and value. POM Tweaks also writes the official _pao_ids selection map and _pao_total amount for integrations that read Product Add-Ons order metadata directly.

For uploads, the visible value is the sanitized file name and compatibility metadata uses an opaque file identifier instead of a reusable public URL. Customer and administrator HTML views generate authorized links at render time. WooCommerce's personal-data export includes the file name, detected MIME type, and size without exposing a storage key or download URL. When WooCommerce's configured order erasure removes personal data, POM removes the linked bytes and private records; ordinary anonymization or retention settings that keep order data also keep its files. If several lines or orders reference the same private file, deleting one reference keeps the file available to the others and removes it only after the final reference disappears.

Flat fees contribute once to _pao_total; quantity-based and percentage-based amounts scale with the ordered quantity. Existing official filters for adding prices to displayed order values and filtering Product Add-Ons line metadata remain available.

The immutable POM snapshot also stores each normalized economic policy, selected tier, resolved rate, applied one-time or subscription phase, contribution before coupon and tax, and the line's protected coupon amount. Visible Product Add-Ons metadata and the official compatibility rows keep their established shape; integrations that need the POM-only policy result should read the POM Store API extension or saved POM snapshot rather than infer it from a translated label.

For an All Products for WooCommerce Subscriptions plan, _pao_total follows the extension's add-on discount decision. Add-on rows keep their compatible raw configuration while the stored total records the effective contribution accepted for the selected plan. WooCommerce Subscriptions and WCS_ATT preserve their own scheme, recurring, initial-payment, resubscribe, and switch metadata alongside the POM snapshot in REST responses and webhooks.

For booking lines, the immutable POM snapshot also records the accepted booking base cost, person count, block count, configured add-on prices, and effective multiplied prices. _pao_total, visible order metadata, REST responses, and webhooks therefore retain the amount accepted at add to cart even if booking or add-on rules change later. Editing an existing booking line reuses that saved booking context; a manually created booking line without an accepted booking context is not repriced because its date, availability, and cost cannot be inferred safely.

Verification

  1. Add the same product twice with different add-on values and confirm WooCommerce creates two cart configurations.
  2. Refresh the cart and confirm the selections and line total remain unchanged.
  3. Increase the quantity and confirm flat fees remain once per line while quantity and percentage amounts scale.
  4. Complete a test order and confirm every accepted value appears below the order line.
  5. Change one reusable-choice price, use Order again, and confirm the current price is used.
  6. Disable that reusable choice and confirm both a restored cart and Order again reject the unavailable selection.
  7. If you use a custom Store API client, submit one valid request, one unknown option UUID, and one request missing a required field.
  8. Repeat the Store API test with addons_configuration and confirm the cart response contains the official extensions.addons data when a flat fee is selected.
  9. Create a manual editable order, add a simple product and a variation, and configure each new line from its Configure add-ons control.
  10. Edit a selection, quantity multiplier, or custom price and confirm the line subtotal, tax, and order total change without removing unrelated metadata.
  11. Open a line with an existing upload and confirm the saved file is linked below Historical selections while the file input remains empty.
  12. Repeat the edit with HPOS enabled and, if applicable, on an editable subscription order.
  13. Inspect the order through WooCommerce REST and an order webhook before and after editing and confirm the visible values, _pao_ids, and _pao_total remain synchronized.
  14. With Bookings active, place a two-block, three-person booking containing person, block, combined percentage, and flat-fee add-ons. Confirm the product preview, Cart Block, _pao_total, order REST response, and webhook use the same accepted add-on total.
  15. Create a subscription with add-ons, change the current add-on definition, and generate a renewal. Confirm the renewal keeps the historical line price, visible values, _pao_ids, _pao_total, and POM snapshot without requiring the current form.
  16. Use Order again for the same product and confirm it uses the current definition and prices instead.
  17. Pay a failed renewal and a delayed initial order. Confirm the renewal stays historical while the initial payment follows the current-definition rule.
  18. Switch a subscription product or variation, select a new add-on value, and confirm the switch order and resulting subscription line retain that accepted configuration.
  19. Open an editable subscription, use Configure add-ons or Edit add-ons, and confirm taxes, totals, REST, and webhook payloads remain synchronized.
  20. Permanently delete a test order and confirm its upload is removed only when no renewal or other order still references it. Repeat with WooCommerce personal-data erasure enabled.
  21. From a classic cart, open a simple line through its product link, change its selection and quantity, select Update cart item, and confirm exactly one replacement line exists with recalculated totals.
  22. Repeat with a variation and an existing upload: keep the upload, replace it, clear it, and cause one validation failure. Confirm the original line and file survive the failure and replaced temporary files are cleaned only after success.
  23. Open one cart line in two tabs. Save the first tab, then submit the second and confirm the stale edit is rejected without changing or duplicating the line. Repeat after removing the line from the cart.
  24. Repeat from Cart and Checkout Blocks through the signed product permalink. Then call /wc/store/v1/cart/extensions with the cart item's edit_token and confirm success returns the complete updated cart while an altered token, another guest session, and a missing required field leave the original cart unchanged.
  25. Add a line with a private upload as a guest, sign in, and confirm the migrated cart can still download and purchase that file while another account cannot access it.
  26. With two country zones configured, add fixed, percentage, tiered, and custom-price fields, then change the checkout country. Confirm each current row uses the second zone, an out-of-range custom price blocks checkout, and returning to the first zone recalculates once without stacking amounts.
  27. Repeat the country change with a trusted official cart_item_data['addons'] row. Confirm an exact field and option match is repriced, while a renamed, reordered, duplicate, or removed definition blocks checkout. Generate a renewal from an existing subscription and confirm its historical row is unchanged.

Troubleshooting and rollback

  • If a restored cart is blocked, remove the affected line and configure the product again from its product page.
  • If Order again cannot restore a line, confirm that its assigned group and selected options still exist and are enabled.
  • If Configure add-ons is absent, confirm the order remains editable, the line resolves to a product or variation, and that product has current fields or a saved POM Product Add-Ons snapshot.
  • If a historical row should be removed, clear its checkbox before selecting Done. Removing a current configuration does not delete unrelated line metadata.
  • If a Store API request fails, confirm the context, field, and option UUIDs belong to the current product definition and that every required value is present.
  • If an edit reports that the line changed or no longer exists, reload the cart and begin again. Do not reuse an earlier tab or copy its edit URL to another browser.
  • To stop new Product Add-On processing, disable Product Add-Ons under Settings → POM Tweaks. Historical order details remain readable.

Related: Storefront fields and pricing, Store API and Blocks, and Official Product Add-Ons compatibility.