Hooks are the supported extension mechanism for B2C Commerce. They let custom code run at specific points in the platform's lifecycle (order created, customer registered, basket calculated) without modifying base code. Used right, they survive every platform upgrade. Used wrong, they reintroduce the customization debt SFRA was meant to solve.
Sapota's Salesforce team has built hook implementations across order processing, customer lifecycle, and basket calculation flows. The patterns below are the ones that produce upgradeable customizations.
What a hook actually is
A hook is a named extension point exposed by the platform. The platform documents which hooks exist (dw.order.calculate, app.order.created, dw.customer.registered, dozens more). Custom code registers a function for the hook; the platform calls the function at the appropriate moment.
Hook registration lives in a cartridge's hooks.json:
{
"hooks": [
{
"name": "dw.order.calculate",
"script": "./hooks/calculateHook.js"
},
{
"name": "app.order.created",
"script": "./hooks/orderCreatedHook.js"
}
]
}
The script file exports the function(s) the hook calls:
// hooks/orderCreatedHook.js
'use strict';
exports.afterPost = function (order) {
// Custom logic when an order is created.
// Examples: notify external system, log audit event,
// populate custom analytics field.
};
The platform invokes afterPost when an order is created. The function receives the order as a parameter.
The hook execution model
Two patterns of hook execution:
Single-script hooks. One cartridge registers; one function runs. Used when the customization is genuinely site-specific.
Multi-script hooks (chained). Multiple cartridges register the same hook. The platform calls each one in cartridge-path order. Each can modify shared state.
The chaining pattern is what makes hooks composable. A Salesforce LINK cartridge for tax integration registers dw.order.calculate. The site's own custom cartridge also registers the same hook. Both fire on order calculation; the order of execution matches cartridge path priority.
For most production sites, the chained pattern is the default. Custom cartridges layer behavior alongside vendor cartridges without colliding.
Common hooks worth knowing
The platform exposes hundreds of hooks. The ones that recur most often:
Order lifecycle:
dw.order.calculate - runs on basket and order calculation
app.order.created - fires when an order is placed
app.order.statusChange - fires when order status changes
dw.order.shipping.applyShippingCost - computes shipping cost
dw.order.payment.authorize - authorizes payment with the gateway
Customer lifecycle:
app.customer.created - fires when a customer registers
app.customer.passwordChanged - fires on password update
app.customer.consent.updated - fires on consent preferences change
Basket lifecycle:
dw.ocapi.shop.basket.beforePOST - validates basket creation
dw.order.coupons.afterApply - fires after coupon code applied
Search and recommendations:
app.search.refinement - extends search refinement logic
app.recommendation.fetch - alters recommendation queries
The full catalog is in Salesforce documentation. Browsing it during integration design surfaces extension points the team did not realize existed.
When to use a hook vs other patterns
Hooks fit a specific extension shape: platform-defined point, custom logic. They are not the right tool for everything.
Use a hook when:
- The platform exposes an explicit extension point for the operation.
- Custom logic should run inside the platform's lifecycle (before or after a built-in step).
- Multiple cartridges may legitimately layer behavior on the same operation.
- Survive-the-upgrade is a priority (it almost always is).
Use a controller append/prepend instead when:
- The customization is at the controller level (request handling, response shaping).
- The logic depends on
req and res context.
Use a script call from a controller instead when:
- The logic is invoked from a custom flow, not a platform lifecycle event.
Use a job instead when:
- The work is scheduled, batch, or otherwise async, not tied to a single request.
The boundaries are fuzzy. Most production sites use hooks alongside controller extensions and scripts. The discipline is matching the tool to the extension shape.
Performance considerations
Hooks fire often. Cart calculation hooks fire on every cart change, every line item update, every promotion application. Order created hooks fire less often but during checkout, a latency-critical flow.
Three rules:
1. Hooks must be fast. A 500ms hook on cart calculation adds 500ms to every cart interaction. Set a budget. Profile.
2. Hooks must not make blocking external calls inline. A hook that calls an external API on every cart update will hang the user. If external integration is needed, queue an async job from the hook; do not block.
3. Hooks should not throw uncaught exceptions. A thrown exception in dw.order.calculate breaks every cart on the site. Catch errors, log them, return cleanly.
The pattern that holds up: small, deterministic, in-memory logic in the hook. External work happens elsewhere.
Sapota's hook discipline
The patterns that ship reliably:
1. One hook file per logical concern. A hook script focused on tax calculation lives in one file. A separate file handles fraud check. Composability via separate scripts even if both register the same platform hook.
2. Defensive coding. Hooks expect the object passed in to be valid. In production, edge cases produce null or malformed inputs. Defensive if checks at the top of every hook prevent cascading failures.
3. Test coverage for hooks specifically. Mock the inputs, invoke the exported function, assert behavior. Hooks are pure functions of their input most of the time, easy to test.
4. Documentation in the hook file itself. A comment block at the top explains what the hook does, why, and which other cartridges' hooks may interact. Future maintainers (and senior reviewers) thank you.
5. Hooks registered with intent. Every hooks.json registration has a code review reviewer who understands when the hook fires. No accidental hook registrations on the wrong event.
Common hook mistakes
Five patterns Sapota has seen on engagements:
1. Slow hooks. A hook on cart calculation that queries the database for each cart line. Cart updates take 5+ seconds. Profile and refactor.
2. External calls in hooks. A hook that calls a marketing API on every order created. The marketing API is slow on some afternoons; orders hang. Move to async job triggered from the hook.
3. Uncaught exceptions. A hook that throws when processing certain product types. The exception breaks calculation site-wide. Add try/catch.
4. Wrong hook for the use case. A team registers app.order.created to send order confirmations. Works in test. In production, the hook fires on partial orders, abandoned carts, and edge cases that the team did not anticipate. Read the hook documentation; use app.order.statusChange with explicit status check instead.
5. Hook chains with order dependencies. Two cartridges register the same hook. One depends on the other completing first. Cartridge path ordering matters. Document the dependency; do not rely on accidental ordering.
What good hook architecture looks like
A B2C Commerce site with healthy hook usage:
- Hooks defined in dedicated
hooks/ folders within cartridges.
- Each hook script focused on a single concern.
- Hooks tested with unit tests, mocking platform objects.
- Hook performance budgets enforced (typical: under 100ms per hook).
- External calls from hooks deferred to async jobs.
- Documentation of why each hook exists and what it does.
The hook system is one of B2C Commerce's most elegant features when used as intended. Programs that lean on hooks for the right extension shape ship customizations that survive every platform upgrade. Programs that bypass them with code modifications inherit the upgrade pain Salesforce was trying to eliminate. Sapota's Salesforce team treats hook discipline as a code review criterion on every B2C Commerce engagement.
Building or auditing hooks in B2C Commerce? Sapota's Salesforce team, certified on B2C Commerce Developer (Comm-Dev-101), handles hook architecture, extension design, and platform-upgrade-safe customization on production engagements. Get in touch ->
See our full platform services for the stack we cover.