Install
$ agentstack add skill-alexkwitko-salesforce-agentforce-salesforce-d2c-setup ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Salesforce D2C Commerce Setup Playbook
Practical, DX-first playbook for standing up a Salesforce D2C (or B2B) Commerce store end to end. Drawn from a real build (a coffee-equipment D2C store, "Bean & Brew"); the patterns apply to any catalog. Substitute your own products/brand.
> Companion reference: [reference/commerce-api-and-data-model.md](reference/commerce-api-and-data-model.md) has the exhaustive catalog — every Connect REST endpoint, the full cart/order/promotions object model, both calc-framework interface tables, the commerce/* LWC adapter list, the B2B-vs-D2C matrix, and the payment-framework objects. Load it when you need exact paths/signatures. This file is the workflow; that file is the catalog. > > Scope note: this is Commerce on the core Salesforce platform (Experience Cloud LWR + the Commerce data model), NOT "B2C Commerce Cloud" (SFRA / PWA Kit / Demandware) — a separate product. The official guide is the B2B & D2C Commerce Developer Guide.
#0 rule: Official-first for COMMERCE PLUMBING; custom LWC for DISTINCTIVE DESIGN
Keep the standard commerce components for the transactional path (PDP, cart, checkout, search) — never re-implement cart/checkout/pricing/tax; use the official mechanisms (Commerce store, OrderDeliveryMethod, CommercePayments, entitlement policies, Guest Buyer Profile, CartExtension calculators). But the OOTB Commerce LWR template's marketing pages (home/landing) are a generic placeholder ("Alpine Energy" banners, footer links all to /) — for a professional, on-brand 2026 storefront you SHOULD build custom LWR design components for the homepage/editorial sections. That's not "Frankenstein," it's the supported path (reference §17). Rule of thumb: custom design components for storytelling/merchandising pages; standard components for the buy-flow.
#1 rule: DX/CLI/API first, UI last
Order for every task:
sfCLI —sf project deploy/retrieve,sf data,sf apex run.- Connect/REST API via CLI —
sf api request rest "/services/data/v62.0/..."(uses the stored session even though the raw token is hidden). - Metadata XML deploy (mdapi when source-format type inference fails).
- Apex self-callout (for APIs with no
sf apipath, e.g. multipart uploads). - Browser/Setup UI — last resort. To drive the org UI headlessly without a password, use
sf org open --path "" --url-only→ navigate the browser to that frontdoor URL (auto-authenticates with the CLI token).sf org displayredacts the access token (returns a 54-char placeholder), so rawcurl -H "Authorization: Bearer ..."401s — usesf api request restor the Apex session instead.
#2 rule: pick the calc framework up front (one or the other, never both)
Cart/checkout calculations run on one of two mutually-exclusive frameworks — they cannot be mixed:
- Legacy "Checkout Integrations" — Apex implements
sfdc_checkout.*interfaces, registered viaRegisteredExternalService+StoreIntegratedService. Fast to mock; what older stores use. - Cart Extensions / Cart Calculate (current GA, Salesforce-recommended) — Apex extends
CartExtension.*classes registered against extension points (e.g.Commerce_Domain_Tax_CartCalculator). Required for the Cart Calculate API and finer control.
Default to legacy only for a quick mock/demo or a store already on it. Choose CartExtension for new builds. Migrating between them is a rebuild of every calculator, not a flag — decide early. (Full interface/extension-point tables + signatures in the reference file §1.) Payment is outside both — always commercepayments (§4).
1. Store + data model (the spine)
Core objects: WebStore, ProductCatalog, ProductCategory, ProductCategoryProduct (product→category junction), Product2, PricebookEntry (standard + the store's pricebook → WebStorePricebook), BuyerGroup, CommerceEntitlementPolicy, CommerceEntitlementBuyerGroup (policy↔group), WebStoreBuyerGroup (store↔group), WebStoreCatalog (store↔catalog).
- The standard "Commerce Store (LWR)" template in some orgs is B2B-only (no separate D2C template); the D2C distinction is buyer access + guest, not a different template. Create the store from the standard template either way.
- Products need a
PricebookEntryin both the standard pricebook and the store pricebook to be sellable. (8 products × 2 = 16 entries in the reference build.) - Add products to a category via
ProductCategoryProduct(ProductCategoryId, ProductId); rebuild the search index afterward (see §8). Product2images are NOT a URL field — see §3.
2. Catalog distinct from other stores
If one org hosts multiple brands/stores, keep catalogs distinct: separate Product2 SKUs per brand, add only the brand's products to that store's category, and (if a category was shared) remove the other brand's ProductCategoryProduct rows. Use a Brand__c picklist on Order/Web_Event__c as a dimension (not a separate identity) when you want one unified customer across brands.
3. Product images — the hard part (CMS multipart upload)
ProductMedia (the image link) requires ProductId + ElectronicMediaId (→ ManagedContent, required) + ElectronicMediaGroupId. Gotchas that cost the most time:
ManagedContentis NOT DML-createable;ManagedContentVersionis not an SObject. Images must be created through the Connect CMS API.- The CMS
source.refwill NOT accept aContentDocument/ContentVersionId ("This image reference isn't available"). - The standard media groups already exist per store: query
ElectronicMediaGroup— you'll find Tile Image, Product List Image, Product Detail Images (+ Banner, Attachments). Reuse them;ElectronicMediaGroupis not createable. - CMS workspace =
ManagedContentSpace(querySELECT Id, Name FROM ManagedContentSpace).
Winning create recipe (POST /connect/cms/contents, multipart):
- JSON part named
content:{"contentSpaceOrFolderId":"","title":"...","urlName":"img-...","contentType":"sfdc_cms__image","contentBody":{"sfdc_cms:media":{"source":{"type":"file"}}}} source.typemust be the constant"file"; do NOT includerefand do NOT includesfdc_cms:altText(both rejected withadditionalPropertieserrors).- Binary part named exactly
contentData(the API demands this name regardless ofref), withfilename+Content-Type: image/png. - Order matters: content part FIRST, then the
contentDatabinary part.
How to send multipart headlessly (sf api request rest can't do multipart; the token is redacted): build the whole multipart body in Python, base64 it, and POST from Apex self-callout:
- Add a
RemoteSiteSettingfor the org My-Domain (deploy as mdapi — source-format.remoteSiteSetting-meta.xmlthrowsTypeInferenceError; use apackage.xml+ mdapi dir). - Apex:
req.setEndpoint('https://.my.salesforce.com/services/data/v62.0/connect/cms/contents'); req.setHeader('Authorization','Bearer '+UserInfo.getSessionId()); req.setHeader('Content-Type','multipart/form-data; boundary=...'); req.setBodyAsBlob(EncodingUtil.base64Decode(b64)); - Anonymous-apex script cap ≈ 1MB → upload one image per run (a batch of 8 base64 bodies = 207KB and still failed "Script too large" when combined with maps; loop one at a time, ~25KB each).
- Generate images with Pillow (available locally) — clean branded PNGs render fine.
Then link + publish:
ProductMediaIS DML-createable:insert new ProductMedia(ProductId=p, ElectronicMediaId=managedContentId, ElectronicMediaGroupId=, SortOrder=n)— one per group (List/Detail/Tile = 3 per product).- Publish the content:
POST /connect/cms/contents/publishbody{"contentIds":[...]}(field iscontentIds, notmanagedContentIds/ids). Returns adeploymentId/#DEPLOYED. - Verify per product via
GET /services/data/v62.0/commerce/webstores/{webStoreId}/products/{productId}→defaultImage.urlpopulated +mediaGroups. The product search API (POST .../search/product-search) is what the LIST page reads — confirm it returnsdefaultImagetoo. - Direct media URLs require the storefront session; a bare-domain
curl301→404s to the wrong site — that's expected, not a defect.
4. Payment (mock for demo, real otherwise)
(Payment uses the commercepayments framework regardless of calc framework — request types, objects, client vs server flow in reference §8.)
- Mock: an Apex
CommercePayments.PaymentGatewayAdapterthat always approves (real adapter branches onctx.getPaymentRequestType(): Tokenize/Authorize/Capture/Refund).PaymentGatewayProvidercan't be inserted via Apex DML — create withsf data create record. ThenPaymentGateway+ aNamedCredential. - Wire to the store:
StoreIntegratedService(StoreId, ServiceProviderType='Payment', Integration=). Verify:SELECT ServiceProviderType, Integration FROM StoreIntegratedService WHERE StoreId='...'.
5. Shipping
OrderDeliveryMethod(e.g. "Standard Shipping",IsActive=true). The standard checkout can use a flat delivery method without a custom Apex shipping service. For dynamic rates, register a shipping integration like tax (§6).
6. Tax (mock = a real store-completeness win)
(This is the legacy sfdc_checkout path — see rule #2. On a CartExtension store, extend CartExtension.TaxCartCalculator against Commerce_Domain_Tax_CartCalculator instead; reference §1.)
- Apex
class BeanBrewMockTax implements sfdc_checkout.CartTaxCalculations { IntegrationStatus startCartProcessAsync(IntegrationInfo, Id cartId) {...} }— compute e.g. 8% perCartItem, writeCartTaxrows. - Gotcha:
sfdc_checkout.IntegrationStatushas nomessagefield — set only.status(SUCCESS/FAILED); log errors viaSystem.debug. - Register:
RegisteredExternalService(DeveloperName, MasterLabel, ExternalServiceProviderType='Tax', ExternalServiceProviderId=)thenStoreIntegratedService(StoreId, ServiceProviderType='Tax', Integration=). Both ARE Apex-DML-creatable.StoreIntegratedService.ServiceProviderTypeenum: Flow, Price, Promotions, Inventory, Shipment, Tax, Payment, Extension.
7. Inventory / supply chain
ProductInventory/OCI is a separate (heavy, often UI-gated) enablement. Without it, products sell as available by default — fine for demos.- For Woo-style stock parity, seed
Product2.Stock_On_Hand__c(this field often already exists from a Woo sync). It's a data layer, not a hard checkout block unless OCI is on.
8. Search index
- Rebuild after catalog/category/image/entitlement changes:
POST /services/data/v62.0/commerce/management/webstores/{id}/search/indexesbody{}. PollGETthe same endpoint untilindexStatus != "InProgress"(≈2–5 min). The LIST page reads the index; PDP reads live.
9. Buyers + entitlement (authenticated)
- A buyer =
Account→BuyerAccount(BuyerId, BuyerStatus='Active', IsActive=true)→BuyerGroupMember(BuyerGroupId, BuyerId).BuyerStatusdefaults toPending— must beActiveor carts fail "Invalid effective accountId." - D2C buyers are Person Accounts.
- The store's buyer group must have an active
CommerceEntitlementPolicy(CanViewProduct/CanViewPrice=true) linked viaCommerceEntitlementBuyerGroup, and the group linked to the store viaWebStoreBuyerGroup. The note in Setup: "each assigned buyer group must have at least one active entitlement." - ⚠️ An entitlement policy with
CanViewProduct=truebut ZEROCommerceEntitlementProductrows entitles NOTHING — there is no "grant all products" boolean field; the storefront returns 404 on every product (guest AND buyer) until you insertCommerceEntitlementProduct(PolicyId, ProductId)rows for the products (or category rows). This is the #1 "catalog is empty for shoppers but admin API sees products" cause. - REST cart as internal admin doesn't work:
POST /commerce/webstores/{id}/carts?effectiveAccountId=Xreturns "Invalid effective accountId" even for a valid Active buyer — D2C checkout runs in the shopper's storefront session (guest or community user), not an admin REST context. To prove checkout, log in as a real buyer (recipe below).
Provisioning a test BUYER LOGIN (to prove checkout headlessly-ish via the live storefront)
A buyer storefront login needs ALL of these — missing any one yields 403/404 on the commerce APIs:
- Portal User on the Person Account (
ContactId = PersonContactId, profile = the store's CspLitePortal "Shopper Profile"). ⚠️ The portal account's OWNER must have aUserRole("portal account owner must have a role") — assign one first. Set the password in a SEPARATE transaction from the insert (System.setPasswordright after insert silently doesn't take for login). - D2C Commerce Shopper permission-set LICENSE (
PermissionSetLicenseDeveloperNameCommerceShopperPsg) assigned viaPermissionSetLicenseAssign— without a commerce PSL the APIs 403. - The standard
CommerceUser("Shopper") permission set (grants the commerce system perm) + a custom permset with Read onProductCatalog/ProductCategory/ProductCategoryProduct/Product2/ProductMedia(the Customer Community license rejectsCreate WebCart— cart objects are handled by the commerce runtime, so don't add cart CRUD). - The Account = Active
BuyerAccount+BuyerGroupMember, and the policy hasCommerceEntitlementProductrows (above). - Re-login after any permset/PSL change — community sessions cache permissions; the storefront keeps 403ing on the old session until you log out/in.
10. Guest browsing + guest checkout (the saga)
- Site public access: in the site's
DigitalExperienceBundle,sfdc_cms__site//content.json→contentBody.authenticationTypemust beAUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED(valid enum; NOTAUTHENTICATED_AND_ANONYMOUS). Deploy (mdapi) +sf community publish --name "". - WebStore flags (Apex updateable):
OptionsGuestBrowsingEnabled,OptionsGuestCartEnabled,OptionsGuestCheckoutEnabled,OptionsPreserveGuestCartEnabled;GuestBuyerProfileId(object prefix3K0, only Id/Name queryable). - CRITICAL — run the official Guest Access automation in Setup UI: Commerce app → Store → Settings → Store → Buyer Access tab → Guest Access → Disable → Enable → Continue. This is the step that assigns object permissions to the guest profile, creates the Guest Buyer Profile, sets person-account default, and sets Experience-Builder public access. Setting the WebStore flags via Apex does NOT run this automation — the UI banner literally says "Click Disable and then Enable to run the full automation." There is no headless API for the guest entitlement/permission assignment.
- Confirm Store Access → Buyer Groups = your buyer group (re-assigning errors as a duplicate = it's wired).
- Rebuild the search index; guest catalog visibility can lag (entitlement + index recalc; minutes to hours in a Dev Edition org). Even with a complete, correct setup the guest grid may stay empty for a while — verify in an authenticated session meanwhile.
- Drive all the Setup-UI steps headlessly via the
sf org open --url-onlyfrontdoor (no password).
Guest visibility = THREE independent gates (all must pass) — the authoritative checklist
A guest 404 on the product API and 403 on pricing almost always means one of these is missing:
- Commerce entitlement — the policy on the store's buyer group needs
CommerceEntitlementProductrows (no "all products" flag; 0 rows ⇒ 404 for everyone). Guests inherit the store buyer group viaWebStoreBuyerGroup; the Manage Guest Access automation associates the Guest Buyer Profile with the buyer group (the guest buyer profile is NOT aBuyerGroupMemberrow — that association is internal, which is why
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: alexkwitko
- Source: alexkwitko/Salesforce-Agentforce
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.