SfSkills Domains Docs GitHub

Home / lwc / commerce-lwc-components

commerce-lwc-components

Use this skill when building or customizing Lightning Web Components for B2B Commerce or D2C LWR storefronts — product display tiles, cart line-item components, checkout step components, wishlist buttons, and product comparison widgets. NOT for standard LWC outside a Commerce store or Aura Community Builder components — use lwc/experience-cloud-lwc-components.

Open the skill folder on GitHub


Commerce LWC Components

Use this skill when an LWC component must read or mutate product, cart, wishlist, or checkout data inside a B2B Commerce or D2C LWR storefront. It covers Commerce Storefront wire adapters (commerce/* modules), CSP capability requirements, Experience Builder registration, and SFDX deployment of storefront components.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

Commerce Storefront Wire Adapters (commerce/*)

Custom LWC components in B2B/D2C LWR stores bind to product, cart, and wishlist data using wire adapters imported from the commerce namespace — for example commerce/productApi, commerce/cartApi, commerce/wishlistApi. These are NOT the same as lightning/uiRecordApi or lightning/uiObjectInfoApi. Using standard LDS adapters inside a store component will either silently return no data or throw import errors at runtime, because LDS is not available in the LWR storefront rendering context.

Wire adapters from the commerce namespace resolve store context automatically: the component does not need to pass a store ID or buyer group ID — these come from the runtime storefront context injected by the LWR framework.

Key adapters:

lightningCommunity__RelaxedCSP Capability

LWR storefront templates disable both Lightning Locker and Lightning Web Security. The lightningCommunity__RelaxedCSP capability, declared in the capabilities array of the .js-meta.xml, signals that a component was authored knowing those sandbox protections are absent.

Know the documented scope, because it is narrower than commonly stated. Salesforce's rule is: "Lightning web components in a managed package that aren't configured with the lightningCommunity__RelaxedCSP tag are disabled in the Components panel in Experience Builder for any site with Lightning Locker disabled." So:

Experience Builder Registration and Design Properties

A component must be exposed in Experience Builder to be drag-and-droppable onto store pages. This requires:

  1. Setting isExposed: true in the .js-meta.xml.
  2. Declaring at least one target inside <targets> — typically lightningCommunity__Page or a specific Commerce store page target.
  3. Optionally declaring <targetConfigs> to expose design-time properties (e.g., product tile image size) that store admins can configure per page.

Components registered without the correct <targets> entries will not appear in the Experience Builder component panel even if the metadata deployment succeeds.


Common Patterns

Pattern 1: Product Display Tile with Storefront Wire Adapter

When to use: Building a custom product card that shows product name, image, description, and negotiated price inside a B2B or D2C LWR store product list page.

How it works:

// productTile.js
import { LightningElement, api, wire } from 'lwc';
import { getProduct } from 'commerce/productApi';
import { getProductPrice } from 'commerce/productApi';

export default class ProductTile extends LightningElement {
    @api recordId; // product ID passed by the store page

    @wire(getProduct, { productId: '$recordId', fields: ['ProductCode', 'Description', 'Name'] })
    product;

    @wire(getProductPrice, { productId: '$recordId' })
    price;

    get productName() {
        return this.product?.data?.fields?.Name?.value;
    }

    get negotiatedPrice() {
        return this.price?.data?.negotiatedPrice;
    }
}

The .js-meta.xml must include:

<capabilities>
    <capability>lightningCommunity__RelaxedCSP</capability>
</capabilities>
<targets>
    <target>lightningCommunity__Page</target>
</targets>

Why not the alternative: Using @wire(getRecord, { recordId: '$recordId', fields: [...] }) from lightning/uiRecordApi will not work. The LWR storefront runtime does not load LDS modules, so the adapter returns no data and no error, silently breaking the component.

Pattern 2: Cart Mutation with Imperative Call

When to use: Building an "Add to Cart" button that adds a product and quantity to the active buyer cart.

How it works:

// addToCartButton.js
import { LightningElement, api } from 'lwc';
import { addItemToCart } from 'commerce/cartApi';

export default class AddToCartButton extends LightningElement {
    @api productId;
    @api quantity = 1;

    async handleAddToCart() {
        try {
            await addItemToCart({ productId: this.productId, quantity: this.quantity });
            this.dispatchEvent(new CustomEvent('cartupdate'));
        } catch (e) {
            // surface error to buyer UI
            console.error('Add to cart failed', e);
        }
    }
}

addItemToCart is an imperative function, not a wire adapter. It returns a Promise and must be called inside a user-interaction handler. Do not call it during connectedCallback or a wire handler — cart mutations triggered outside user gestures may be blocked by the storefront security context.


Decision Guidance

SituationRecommended ApproachReason
Reading product data for display@wire(getProduct, ...) from commerce/productApiDeclarative binding; store context auto-injected
Reading negotiated price for a buyer@wire(getProductPrice, ...) from commerce/productApiPrice depends on buyer account; standard LDS has no price concept
Adding or removing cart itemsImperative addItemToCart / removeItemFromCart from commerce/cartApiMutations must be user-triggered; wire is read-only
Displaying wishlist state@wire(getWishlist, ...) from commerce/wishlistApiWishlist is buyer-scoped; standard LDS cannot resolve it
Component not showing in Experience BuilderVerify isExposed: true and correct <targets> in .js-meta.xmlRegistration issue, not a code issue
Managed-package component missing from the Experience Builder Components panelAdd lightningCommunity__RelaxedCSP capability to meta XMLDocumented: managed-package LWCs without the tag are disabled in the panel on any site with Locker disabled

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Confirm store type and target page. Identify whether the store is B2B Commerce or D2C, and which page type the component will be placed on (product detail page, cart page, checkout step, etc.). This determines which commerce/* adapters are available and which Experience Builder page targets to register.
  2. Select the correct Commerce wire adapters. Import from commerce/productApi, commerce/cartApi, or commerce/wishlistApi as appropriate. Do not use lightning/uiRecordApi or lightning/uiObjectInfoApi — these modules are unavailable in the LWR storefront runtime.
  3. Scaffold the LWC bundle. Create the .html, .js, and .js-meta.xml files. In the JS, wire or import from the correct commerce/* module. Expose reactive getters for template binding rather than accessing .data directly in the template.
  4. Configure the meta XML correctly. Set isExposed: true, add lightningCommunity__RelaxedCSP to <capabilities>, and add the correct <targets> entry. Expose design-time properties via <targetConfigs> if store admins need to configure the component.
  5. Deploy via SFDX. Run sfdx force:source:push or sf project deploy start targeting the store org. Verify the component appears in Experience Builder under Custom Components.
  6. Test in Experience Builder preview and live store. CSP and adapter behavior can differ between Builder preview mode and live store rendering. Always test in both contexts before release.
  7. Validate and review. Run python3 scripts/check_commerce_lwc_components.py --manifest-dir force-app/main/default/lwc/ to catch missing CSP declarations and incorrect adapter imports.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Missing lightningCommunity__RelaxedCSP hides managed-package components from the Components panel — the documented consequence is that managed-package LWCs without the tag are disabled in the Experience Builder Components panel on any site with Locker disabled, so an admin cannot place them at all. It is not a runtime rendering failure, and the distinction matters for triage: if the component is already on the page, this capability is not your bug. Declaring it on org-local storefront components is harmless convention, not a documented requirement.
  2. getProduct fields list is not free-form — The fields parameter for getProduct must use field names in the exact format expected by the Commerce Product API, not the same format as lightning/uiRecordApi. For example, Name works but Product2.Name does not. Passing unsupported field names silently returns undefined for those fields rather than throwing an error.
  3. LDS is unavailable; @wire(getRecord) returns no data and no error — In the LWR storefront runtime, lightning/uiRecordApi adapters are not loaded. A wire adapter imported from lightning/uiRecordApi will resolve its import successfully at compile time but never deliver data at runtime, producing no JavaScript error. This means a developer testing in App Builder will see data but a buyer in the store will see a blank component.

Output Artifacts

ArtifactDescription
<componentName>.jsLWC controller with commerce/* wire adapter imports and reactive getters
<componentName>.htmlTemplate referencing reactive getter properties, not raw .data access
<componentName>.js-meta.xmlMeta XML with lightningCommunity__RelaxedCSP, isExposed: true, and correct <targets>
Deployment confirmationOutput of sf project deploy start confirming component is registered in the store org

Package files