Loading...

B2C Commerce Hooks: Extending Without Breaking Upgrades

Hooks are the supported extension mechanism for B2C Commerce. They let custom code run at specific points in the platform's lifecycle without modifying base code. Used right, they survive every platform upgrade. Used wrong, they reintroduce the customization debt SFRA was meant to solve.

B2C Commerce Hooks: Extending Without Breaking Upgrades

Key takeaways

  • Hooks are the supported extension mechanism. The platform exposes named extension points (dw.order.calculate, app.order.created, dw.customer.registered, dozens more); custom code registers functions on hooks; the platform calls them at the appropriate lifecycle moment. Used right, they survive every platform upgrade.
  • Hooks differ from controller append/prepend/replace by lifecycle layer. Controllers extend request handling; hooks extend platform business logic that runs outside any specific controller (basket calculation, scheduled jobs, internal workflows). Pick hooks for platform events, controllers for request flow.
  • Performance matters in calculation hooks. dw.order.calculate runs on every basket update — including AJAX cart updates. A 100ms slow database call in a calculate hook becomes a 100ms latency on every add-to-cart. Profile hooks before promoting; move expensive work to async or to less frequent lifecycle events.
  • Hook implementations belong in dedicated hook files registered in package.json. Scattering hook implementations across controllers or helpers makes the customisation surface invisible at code review. Document the hook registration so the cartridge-path effect on hook resolution is auditable.

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.

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project