sales-app-extensibility
Sales App Extensibility
When this skill applies
Use this skill when building, customizing, or deploying extensions for VTEX Sales App.
- Adding features to the cart page (promotions, loyalty, services)
- Adding features to the Product Detail Page (badges, recommendations, warranties)
- Adding features to the menu (user profile, navigation, metrics)
- Integrating external APIs into Sales App extensions
- Generating, scaffolding, or validating extension code for Sales App
Do not use this skill for:
- Regular FastStore storefront customization (use
faststore-storefront) - Building VTEX IO apps (use
vtex-io-*skills) - Sales App core development or framework modifications
Prerequisite: FastStore project
The project root must contain: biome.json, faststore.json, package.json, tsconfig.json, turbo.json.
If any are missing, STOP. The user must install FastStore first:
npx @vtex/fsp-cli init
# Prompt: "What is the application name?" → enter name or press Enter for default
cd <application-name> && yarn
Documentation: https://beta.fast.store/getting-started
Prerequisite: Sales App module
Inside the FastStore project, a Sales App workspace must exist (typically at packages/sales-app) with src/, package.json, and tsconfig.json. Check root package.json "workspaces" for a path containing sales-app. If missing, STOP:
yarn add @vtex/sales-app -D -W
npx fsp create
# Prompts: account name → "Sales App" → path (default or custom)
# Then add the path to root package.json "workspaces" array
yarn install
Documentation: https://beta.fast.store/sales-app/setting-up
Do NOT proceed to discovery or code generation until both prerequisites are confirmed.
Decision rules
Follow the mandatory 6-step workflow in order. Do not skip steps.
| Step | Purpose | Gate |
|---|---|---|
| 0 | Check prerequisites | FastStore + Sales App installed → proceed; otherwise STOP |
| 1 | Discovery | Understand what the user wants to build |
| 2 | Requirements & Plan | Map requirements → generate plan → wait for user approval |
| 3 | Code Generation & Validation | Generate component + CSS + index.tsx → validate |
| 4 | Documentation | Generate docs/<ExtensionName>.md explaining the extension |
| 5 | Local Testing | Provide dev commands and URLs |
| 6 | Build & Deploy | Build command → deployment guide |
Extension point selection
| Extension Point | Category | Available Hooks | Layout Shift |
|---|---|---|---|
cart.cart-list.after |
Cart | useCart, useExtension | No |
cart.cart-item.after |
Cart | useCart, useCartItem, useExtension | Yes |
cart.order-summary.after |
Cart | useCart, useExtension | Yes |
pdp.sidebar.before |
PDP | usePDP, useCart, useExtension | Yes |
pdp.sidebar.after |
PDP | usePDP, useCart, useExtension | Yes |
pdp.content.after |
PDP | usePDP, useCart, useExtension | Yes |
menu.item |
Menu | useExtension | No |
menu.drawer-content |
Menu | useCurrentUser, useExtension | No |
Hook availability
- useCart → all cart + PDP extensions
- useCartItem →
cart.cart-item.afteronly - useCurrentUser →
menu.drawer-contentonly - usePDP → PDP extensions only
- useExtension → all extension points
Template selection
- No API + no hooks → simple template
- No API + hooks → hook template
- API + no auth → API template
- API + VTEX IO proxy → IO proxy template (recommended)
- API + direct auth → direct auth template (insecure, warn user)
- API doc provided → generate TypeScript interfaces from extracted response shapes; no API doc → use
${DATA_INTERFACE}placeholder
API authentication strategy
- Recommended: VTEX IO Proxy App — IO app stores keys server-side. Extension uses
credentials: 'include'with a relative path (/_v/my-api/data). - Insecure: Direct Auth — Keys in frontend code, visible in browser. Testing/development only.
Hard constraints
Constraint: Component must return JSX.Element, never null
defineExtensions expects ExtensionPointComponent which returns Element, not Element | null.
Why this matters
Returning null causes a TypeScript compilation error. The build will fail.
Detection
return null in any component registered with defineExtensions.
Correct
export function MyExtension(): JSX.Element {
if (!data) return (<></>);
return <div>{data.value}</div>;
}
Wrong
export function MyExtension(): JSX.Element | null {
if (!data) return null;
return <div>{data.value}</div>;
}
Constraint: Guard optional properties before use
CartItem.manualPrice (number | undefined), CartItem.productRefId (string | undefined), CartItem.attachments (Attachment[] | undefined) must be guarded.
Why this matters TypeScript strict mode rejects accessing possibly-undefined values.
Detection
item.manualPrice, item.productRefId, or item.attachments without ?., ??, &&, or != null.
Correct
const price = item.manualPrice ?? item.sellingPrice;
const refId = item.productRefId ?? 'N/A';
const count = item.attachments?.length ?? 0;
Wrong
const price = item.manualPrice;
const refId = item.productRefId.toUpperCase();
const count = item.attachments.length;
Constraint: useCartItem().item may be undefined
item from useCartItem() is CartItem | undefined.
Why this matters
Accessing properties on undefined causes a runtime crash.
Detection
Destructured item from useCartItem() used without if (!item) guard.
Correct
const { item } = useCartItem();
if (!item) return (<></>);
return <div>{item.name}</div>;
Wrong
const { item } = useCartItem();
return <div>{item.name}</div>;
Constraint: Never expose authentication keys in frontend code
API keys in fetch headers are visible in the browser network tab.
Why this matters Leaked keys compromise the external service.
Detection
Literal auth header values in fetch(): 'x-api-key': '...', 'Authorization': 'Bearer ...'.
Correct
const response = await fetch('/_v/my-api/data', {
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
});
Wrong
const response = await fetch('https://api.example.com/data', {
headers: { 'x-api-key': 'sk-secret-key-12345' },
});
Constraint: IO Proxy must use relative paths only
The Sales App internal proxy resolves the domain automatically.
Why this matters A full URL bypasses the proxy, breaking session cookies and causing CORS errors.
Detection
fetch('https://{account}.myvtex.com/...') in IO proxy components.
Correct
await fetch('/_v/my-loyalty-api/points', { credentials: 'include' });
Wrong
await fetch('https://myaccount.myvtex.com/_v/my-loyalty-api/points');
Constraint: defineExtensions is required in index.tsx
Entry point must use defineExtensions from @vtex/sales-app.
Why this matters Without it, the build succeeds but no extensions render.
Detection
Missing defineExtensions import or call in index.tsx.
Correct
import { defineExtensions } from '@vtex/sales-app';
import { MyExtension } from './components/MyExtension';
export default defineExtensions({ 'cart.cart-list.after': MyExtension });
Wrong
import { MyExtension } from './components/MyExtension';
export default { 'cart.cart-list.after': MyExtension };
Constraint: Extension point names must match exactly
IDs are a fixed set. Non-existent names silently fail.
Why this matters The extension renders nowhere. No build error — invisible at runtime.
Detection
Any name not in: cart.cart-list.after, cart.cart-item.after, cart.order-summary.after, pdp.sidebar.before, pdp.sidebar.after, pdp.content.after, menu.item, menu.drawer-content.
Correct
defineExtensions({ 'cart.cart-list.after': MyExtension });
Wrong
defineExtensions({ 'cart.list.after': MyExtension });
Constraint: Hooks must be used in compatible extension points
Each hook has an available_in restriction.
Why this matters Hook context is only mounted for specific extension points. Using elsewhere throws runtime errors.
Detection
useCartItem in PDP/menu. useCurrentUser in cart. usePDP in menu/cart-list.
Correct
// useCartItem in cart.cart-item.after ✓
defineExtensions({ 'cart.cart-item.after': ItemWarranty });
Wrong
// useCartItem in pdp.sidebar.after ✗ — will fail at runtime
defineExtensions({ 'pdp.sidebar.after': ItemWarranty });
Constraint: Always present execution plan and wait for approval
Do not generate code until the user confirms the plan.
Why this matters Generating without agreement wastes effort and may produce the wrong extension.
Detection Jumping from discovery to code generation without presenting a plan.
Correct Discovery → Requirements → Present plan → User approves → Generate code.
Wrong Discovery → Generate code immediately.
Preferred pattern
Workflow overview
Step 0 — Check FastStore + Sales App prerequisites. STOP if missing.
Step 1 — Discovery. Detect use case from keywords, ask follow-up questions, determine API auth strategy. If the user provides API documentation (URL, OpenAPI/Swagger file, Markdown, or inline text), ingest it to extract endpoint details and response shapes — skip the equivalent manual questions. Validate extracted information with the user. Load the discovery reference for detailed question flows and the API Documentation Ingestion section.
Step 2 — Map requirements to extension point + hooks + template. Present plan. Wait for approval.
Step 3 — Generate component, CSS, and index.tsx. Validate against the 10-point checklist. If API documentation was ingested in Step 1, generate TypeScript interfaces from the extracted response shapes and use them in the component instead of the ${DATA_INTERFACE} placeholder. If the extension calls 2+ endpoints, extract fetch logic into custom hook(s). Load the code templates reference for all template patterns, the type generation rules, the custom fetch hook pattern, and the types reference for hook return types and TypeScript definitions.
Step 4 — Generate documentation file at docs/<ExtensionName>.md inside the Sales App package. Create the docs/ folder if it does not exist. The document must contain:
- Extension name — title matching the component name.
- Overview — one-paragraph summary of what the extension does and why it was built.
- Extension point — which extension point it registers on (e.g.,
cart.cart-list.after) and why that point was chosen. - Hooks used — list each hook (
useCart,usePDP, etc.) with a brief explanation of what data it provides to this extension. - Component structure — description of the component tree, props, and state management.
- Styling — CSS module file name and summary of key classes.
- API integration (if applicable) — endpoint, auth strategy (IO Proxy or direct), request/response shape.
- Source documentation: URL or file path of the original API documentation, if provided.
- Generated types: list of TypeScript interfaces generated from the API documentation.
- Note which fields are optional (
?) vs required per the documentation.
- How to test — dev server command and URL to reach the extension.
- Known constraints — any guards, edge cases, or limitations (e.g.,
useCartItem().itemmay be undefined).
Template for the documentation file:
# <ExtensionName>
## Overview
<One-paragraph description of the extension purpose and value.>
## Extension point
- **Point:** `<extension.point.name>`
- **Rationale:** <Why this extension point was selected.>
## Hooks used
| Hook | Purpose |
|------|---------|
| `useCart` | <What data it provides here> |
## Component structure
<Describe the component tree, key props, and internal state.>
## Styling
- **File:** `<ComponentName>.module.css`
- <Summary of key CSS classes and design decisions.>
## API integration
- **Endpoint:** `/_v/...`
- **Auth strategy:** IO Proxy / Direct / None
- **Request/Response:** <Brief shape description.>
## How to test
Run `yarn fsp dev {account}` and navigate to `https://{account}.myvtex.com/sales-app/...` to verify the extension renders.
## Known constraints
- <Guard or limitation 1>
- <Guard or limitation 2>
Step 5 — Provide local dev commands and test URLs. Load the dev/build/deploy reference.
Step 6 — Build command and deployment guide. Load the dev/build/deploy reference.
Reference Files
Load these on demand based on what the task requires. Do not load all of them upfront.
| File | Load when… |
|---|---|
| references/extension-points-hooks-and-types.md | Choosing an extension point, selecting hooks, looking up TypeScript types (CartItem, ProductSku, Totalizers, Attachment), or checking hook return values and availability per extension point |
| references/code-templates-and-patterns.md | Generating extension code — simple, hook, API, IO Proxy, or Direct Auth templates; CSS module pattern; index.tsx with defineExtensions; hook initialization; validation checklist |
| references/discovery-and-use-cases.md | Running Step 1 (Discovery) — use case detection keywords, follow-up questions, API auth decision tree, IO Proxy vs Direct Auth flow |
| references/local-dev-build-and-deploy.md | Running Steps 5–6 — dev server commands, test URLs, build command, common build errors, FastStore WebOps deployment, monitoring, rollback |
Common failure modes
- Returning null instead of empty fragment —
return nullnot allowed; usereturn (<></>). Build fails. - Accessing item.manualPrice without guard — TypeScript "possibly undefined" error. Use
??. - Using useCartItem outside cart.cart-item.after — Hook context only exists there. Runtime error.
- Hardcoding auth tokens in frontend fetch — Keys visible in browser. Use VTEX IO proxy.
- Using full URL with IO Proxy — Bypasses internal proxy. Use relative path
/_v/.... - Placing code outside packages/sales-app/src/ — Not included in build.
- Missing defineExtensions in index.tsx — Extension compiles but never renders.
- Skipping prerequisite checks — Generating code without FastStore/Sales App installed.
- Not presenting plan — User may want a different approach. Always confirm.
- Invented extension point names —
cart.list.afterinstead ofcart.cart-list.afterfails silently. - Skipping documentation — Extension generated without
docs/<ExtensionName>.md. Future developers won't understand the extension's purpose, hooks, or constraints. - Inventing API response types — Generated interface doesn't match actual API. If documentation was provided, derive types from it; if not, ask the user for a sample JSON response.
- Ignoring provided API documentation — User provided a URL or file but agent asked manual questions anyway. Always check for documentation first and use the API Documentation Ingestion flow.
- Inline fetch with 2+ endpoints — Multiple
fetchcalls inside the component body. Extract into custom hook(s) inhooks/use{Purpose}.ts.
Review checklist
- FastStore installed (biome.json, faststore.json, package.json, tsconfig.json, turbo.json)?
- Sales App module installed (src/, package.json, tsconfig.json in sales-app directory)?
- Discovery completed and use case identified?
- Execution plan approved by user?
- Extension point is valid (from the 8-point reference)?
- Hooks compatible with chosen extension point?
- Component returns JSX.Element, never null?
- Optional properties guarded (manualPrice, productRefId, attachments)?
- useCartItem().item checked for undefined?
- No auth credentials exposed in frontend code?
- IO Proxy uses relative path (/_v/...)?
- defineExtensions configured in index.tsx?
- CSS file path matches component name?
- Documentation generated at
docs/<ExtensionName>.md? - Documentation covers overview, extension point, hooks, structure, and constraints?
- If API documentation was provided, TypeScript interfaces match the documented response shape?
- If 2+ API endpoints used, fetch logic extracted into custom hook(s) in
hooks/? - Build passes:
yarn fsp build {account} sales-app? - Tested locally:
yarn fsp dev {account}?
Related skills
faststore-storefront— storefront customization outside Sales Appvtex-io-app-contract— building VTEX IO proxy apps for secure API integration
Reference
- Sales App setup: https://beta.fast.store/sales-app/setting-up
- FastStore getting started: https://beta.fast.store/getting-started
- VTEX Sales App documentation: https://help.vtex.com/en/tracks/instore-getting-started-and-setting-up
- VTEX IO app development: https://developers.vtex.com/docs/guides/vtex-io-documentation-developing-an-app
More from vtex/skills
vtex-io-react-apps
Apply when building React components under react/ or configuring store blocks in store/ for VTEX IO apps. Covers interfaces.json, contentSchemas.json for Site Editor, VTEX Styleguide for admin apps, and css-handles for storefront styling. Use for creating custom storefront components, admin panels, pixel apps, or any frontend development within the VTEX IO react builder ecosystem.
216vtex-io-masterdata
Apply when working with MasterData v2 entities, schemas, or MasterDataClient in VTEX IO apps, or when anyone designing or implementing a solution must scrutinize whether Master Data is the correct storage. The skill prompts hard questions: native Catalog or other VTEX stores, OMS, or an external database may be better; do not default to MD because it is convenient. Covers JSON Schema, CRUD, triggers, search and scroll, schema lifecycle, purchase-path avoidance, single source of truth, and BFF handoffs. Use for justified custom persistence while avoiding the 60-schema limit.
209vtex-io-storefront-react
Apply when building VTEX IO storefront components under react/ for Store Framework experiences. Covers storefront component structure, css-handles, storefront context hooks, browser-safe data fetching, and how components should behave when used as theme blocks. Use for custom storefront UI, product widgets, banners, forms, or reviewing shopper-facing React code in VTEX IO apps.
207vtex-io-service-apps
Apply when building backend service apps under node/ in a VTEX IO project or configuring service.json routes. Covers the Service class, middleware functions, ctx.clients pattern, JanusClient, ExternalClient, MasterDataClient, and IOClients registration. Use for implementing backend APIs, event handlers, or integrations that must use @vtex/api clients instead of raw HTTP libraries.
206vtex-io-application-performance
Apply when improving VTEX IO Node or .NET services for latency, throughput, and resilience: in-process LRU, VBase, stale-while-revalidate, AppSettings loading, request context, parallel client calls, and avoiding duplicate work. Covers application-level performance patterns that complement edge/CDN caching. Use when optimizing backends beyond route-level Cache-Control.
203vtex-io-app-settings
Apply when defining, validating, or consuming VTEX IO app settings. Covers settingsSchema, app-level configuration boundaries, and how backend or frontend code should depend on settings safely. Use for merchant-configurable behavior, settings forms, or reviewing whether settings belong in app configuration rather than hardcoded logic or custom data entities.
203