Using drop-ins
Drop-in components add Commerce functionality to your storefront. The Commerce boilerplate includes all drop-ins pre-installed—no package installation needed.
Key terms
Before you start, understand these essential terms:
Initializer
A JavaScript file that automatically configures a drop-in when imported (sets GraphQL endpoint, loads translations, registers the drop-in).
Container
A pre-built UI component that renders drop-in functionality (for example, SignIn, CartSummaryList, OrderSummary).
Provider
The render function that mounts containers into your blocks. Each drop-in exports its own provider.
Decorate function
The standard block entry point where you render containers (export default async function decorate(block)).
How to use drop-ins
Three steps: import the initializer, import the container, and render it. Most blocks use a single container.
Import the initializer
Import the initializer for the drop-in. This configures the GraphQL endpoint, loads placeholder text, and registers the drop-in.
// Import initializer (side-effect import handles all setup)import '../../scripts/initializers/auth.js';Import the container
Import the container you need and the render provider. Import maps in head.html resolve paths to the optimized code.
// Import the containerimport { SignIn } from '@dropins/storefront-auth/containers/SignIn.js';
// Import the providerimport { render as authRenderer } from '@dropins/storefront-auth/render.js';Render the container
Render the container in the decorate function of your block. Pass configuration options to customize behavior.
import { rootLink } from '../../scripts/commerce.js';
export default async function decorate(block) { await authRenderer.render(SignIn, { routeForgotPassword: () => rootLink('/customer/forgot-password'), routeRedirectOnSignIn: () => rootLink('/customer/account'), })(block);}Complete example:
import { SignIn } from '@dropins/storefront-auth/containers/SignIn.js';import { render as authRenderer } from '@dropins/storefront-auth/render.js';import { rootLink } from '../../scripts/commerce.js';import '../../scripts/initializers/auth.js';
export default async function decorate(block) { await authRenderer.render(SignIn, { routeForgotPassword: () => rootLink('/customer/forgot-password'), routeRedirectOnSignIn: () => rootLink('/customer/account'), })(block);}Drop-in specific guides
Each drop-in has its own Quick Start page with package names, versions, and drop-in-specific requirements:
- Cart
- Checkout
- Order
- Payment Services
- Personalization
- Product Details
- Product Discovery
- Recommendations
- User Account
- User Auth
- Wishlist
Advanced patterns
These patterns show how to handle more complex scenarios in your blocks.
Multiple containers in one block
Most blocks use a single container, but complex blocks can render multiple containers together. Use Promise.all() to render them in parallel for better performance. The Cart block demonstrates this pattern:
import '../../scripts/initializers/cart.js';import { render as provider } from '@dropins/storefront-cart/render.js';import CartSummaryList from '@dropins/storefront-cart/containers/CartSummaryList.js';import OrderSummary from '@dropins/storefront-cart/containers/OrderSummary.js';import { rootLink, getProductLink } from '../../scripts/commerce.js';
export default async function decorate(block) { // Create layout structure const fragment = document.createRange().createContextualFragment(` <div class="cart__list"></div> <div class="cart__order-summary"></div> `);
const $list = fragment.querySelector('.cart__list'); const $summary = fragment.querySelector('.cart__order-summary'); block.appendChild(fragment);
// Helper to create product links const createProductLink = (product) => getProductLink(product.url.urlKey, product.topLevelSku);
// Render multiple containers in parallel await Promise.all([ provider.render(CartSummaryList, { routeProduct: createProductLink, enableRemoveItem: true, })($list),
provider.render(OrderSummary, { routeCheckout: () => rootLink('/checkout'), })($summary), ]);}Nesting containers with slots
Render containers inside other container slots for advanced composition. Use conditional logic to control which slots render:
import '../../scripts/initializers/cart.js';import { render as provider } from '@dropins/storefront-cart/render.js';import OrderSummary from '@dropins/storefront-cart/containers/OrderSummary.js';import EstimateShipping from '@dropins/storefront-cart/containers/EstimateShipping.js';import Coupons from '@dropins/storefront-cart/containers/Coupons.js';import { readBlockConfig } from '../../scripts/aem.js';import { rootLink, getProductLink } from '../../scripts/commerce.js';
export default async function decorate(block) { const { 'enable-estimate-shipping': enableEstimateShipping = 'false' } = readBlockConfig(block); const createProductLink = (product) => getProductLink(product.url.urlKey, product.topLevelSku);
await provider.render(OrderSummary, { routeProduct: createProductLink, routeCheckout: () => rootLink('/checkout'), slots: { EstimateShipping: async (ctx) => { if (enableEstimateShipping === 'true') { const wrapper = document.createElement('div'); await provider.render(EstimateShipping, {})(wrapper); ctx.replaceWith(wrapper); } }, Coupons: (ctx) => { const coupons = document.createElement('div'); provider.render(Coupons)(coupons); ctx.appendChild(coupons); }, }, })(block);}Combining multiple drop-ins
Use multiple drop-ins in a single block when functionality overlaps. The Cart block combines Cart and Wishlist:
// Import event busimport { events } from '@dropins/tools/event-bus.js';
// Import initializers for both drop-insimport '../../scripts/initializers/cart.js';import '../../scripts/initializers/wishlist.js';
// Import from both drop-insimport { render as provider } from '@dropins/storefront-cart/render.js';import CartSummaryList from '@dropins/storefront-cart/containers/CartSummaryList.js';import * as Cart from '@dropins/storefront-cart/api.js';
import { render as wishlistRender } from '@dropins/storefront-wishlist/render.js';import { WishlistToggle } from '@dropins/storefront-wishlist/containers/WishlistToggle.js';import { WishlistAlert } from '@dropins/storefront-wishlist/containers/WishlistAlert.js';
import { getProductLink } from '../../scripts/commerce.js';
export default async function decorate(block) { // Create notification area const fragment = document.createRange().createContextualFragment(` <div class="cart__notification"></div> `); const $notification = fragment.querySelector('.cart__notification'); block.appendChild(fragment);
// Wishlist route const routeToWishlist = '/wishlist';
// Helper to create product links const createProductLink = (product) => getProductLink(product.url.urlKey, product.topLevelSku);
// Render cart with wishlist functionality in slots await provider.render(CartSummaryList, { routeProduct: createProductLink, slots: { Footer: (ctx) => { // Add wishlist toggle to each cart item const $wishlistToggle = document.createElement('div'); $wishlistToggle.classList.add('cart__action--wishlist-toggle');
wishlistRender.render(WishlistToggle, { product: ctx.item, removeProdFromCart: Cart.updateProductsFromCart, })($wishlistToggle);
ctx.appendChild($wishlistToggle); }, }, })(block);
// Listen for wishlist events events.on('wishlist/alert', ({ action, item }) => { wishlistRender.render(WishlistAlert, { action, item, routeToWishlist, })($notification);
setTimeout(() => { $notification.innerHTML = ''; }, 5000); });}Using API functions without containers
Call API functions directly for programmatic control without rendering UI:
import '../../scripts/initializers/cart.js';import * as Cart from '@dropins/storefront-cart/api.js';import { events } from '@dropins/tools/event-bus.js';
export default async function decorate(block) { // Get cached cart data synchronously const cachedCart = Cart.getCartDataFromCache(); console.log('Cached cart:', cachedCart);
// Fetch fresh cart data const freshCart = await Cart.getCartData(); console.log('Fresh cart:', freshCart);
// Add products programmatically const button = block.querySelector('.add-to-cart-button'); if (button) { button.addEventListener('click', async () => { try { await Cart.addProductsToCart([ { sku: 'ABC123', quantity: 1 } ]); console.log('Product added successfully'); } catch (error) { console.error('Failed to add product:', error); } }); }
// Update cart totals in custom UI events.on('cart/data', (cartData) => { const totalElement = block.querySelector('.cart-total'); if (totalElement && cartData?.prices?.grandTotal) { totalElement.textContent = cartData.prices.grandTotal.value; } }, { eager: true });}Error handling
Handle errors gracefully with try/catch blocks and user notifications:
import createMiniPDP from '../../scripts/components/commerce-mini-pdp/commerce-mini-pdp.js';import createModal from '../modal/modal.js';import { fetchPlaceholders } from '../../scripts/commerce.js';
export default async function decorate(block) { const placeholders = await fetchPlaceholders(); let currentModal = null;
// Custom message display function const showMessage = (message) => { const messageEl = block.querySelector('.mini-cart__message'); if (messageEl) { messageEl.textContent = message; messageEl.classList.add('visible'); setTimeout(() => messageEl.classList.remove('visible'), 3000); } };
async function handleEditButtonClick(cartItem) { try { // Attempt to load and show mini PDP const miniPDPContent = await createMiniPDP(cartItem); currentModal = await createModal([miniPDPContent]);
if (currentModal.block) { currentModal.block.setAttribute('id', 'mini-pdp-modal'); }
currentModal.showModal(); } catch (error) { console.error('Error opening mini PDP modal:', error);
// Show error message using mini-cart's message system showMessage(placeholders?.Global?.ProductLoadError || 'Failed to load product'); } }
// ... rest of block implementation}What the boilerplate provides
The boilerplate includes everything you need:
- Drop-in packages installed in
package.json. - Optimized code in
scripts/__dropins__/. - Import maps in
head.html. - Initializers in
scripts/initializers/for automatic setup. - Example blocks demonstrating usage.
How it works
The following diagram shows how drop-ins integrate into your boilerplate project:
Additional concepts
Additional terms you’ll encounter as you work with drop-ins.
Drop-in
A self-contained Commerce component (Cart, Checkout, Product Details) that includes containers, API functions, and events.
Import maps (in head.html)
Configuration that maps clean import paths (for example, @dropins/storefront-cart) to optimized code in scripts/__dropins__/. The boilerplate includes these pre-configured.
Event bus (@dropins/tools/event-bus.js)
Pub/sub system for drop-in communication. Drop-ins emit events when state changes (for example, cart/data, checkout/updated). Listen to events to update custom UI or trigger logic.
API functions
Programmatic interfaces to control drop-in behavior without rendering UI. Fetch data, trigger actions, and read cached state (for example, Cart.addProductsToCart(), Cart.getCartData()).
Slots
Extension points in containers where you can inject custom content or replace default behavior. Used for deep customization beyond configuration options.
Summary
The boilerplate makes using drop-ins straightforward: import an initializer for automatic setup, import the containers you need, render them with configuration options, and optionally listen to events for custom behavior. No manual package installation or complex configuration required.