Skip to content

Hook System

For usage patterns and examples, see the Use Hooks guide. For checkout pipeline details, see the Hook Pipeline explanation.

type BeforeHook<TData> = (args: {
data: TData;
operation: HookOperation;
context: HookContext;
}) => Promise<TData> | TData;

Before hooks receive the incoming data, may mutate or replace it, and return the (possibly modified) data. Throwing aborts the operation.

type AfterHook<TData> = (args: {
data: TData | null;
result: TData;
operation: HookOperation;
context: HookContext;
}) => Promise<void> | void;

After hooks receive both the original input (data) and the operation result. They cannot alter the result. Throwing does not roll back the operation unless executed inside a transaction.

type HookOperation =
| "create"
| "update"
| "delete"
| "read"
| "list"
| "statusChange"
| "addItem"
| "removeItem"
| "custom";
type HookOrigin = "rest" | "local";

interface HookContext {
actor: Actor | null;
tx: unknown;
logger: Logger;
services: ServiceContainer;
context: Record<string, unknown>;
requestId: string;
origin: HookOrigin;
jobs: JobsAdapter;
db: PluginDb;
kernel: unknown;
}
FieldTypeDescription
actorActor | nullThe authenticated user or API key. null for anonymous operations.
txunknownCurrent database transaction handle. Cast to TxContext at usage sites.
loggerLoggerScoped Pino logger with info, warn, error methods.
servicesServiceContainerAll registered service instances (catalog, orders, inventory, etc.).
contextRecord<string, unknown>Mutable bag for passing data between hooks within the same operation.
requestIdstringUnique identifier for the current request. Same value across all hooks in one request.
originHookOrigin"rest" (HTTP request) or "local" (direct kernel call).
jobsJobsAdapterAdapter for enqueueing background jobs.
dbPluginDbDrizzle database instance. Typed — use directly without casting.
kernelunknownThe kernel singleton. Cast to Kernel to access kernel.services, kernel.database.db, kernel.hooks.
interface Actor {
type: "user" | "api_key";
userId: string;
email: string | null;
name: string;
vendorId: string | null;
organizationId: string | null;
role: string;
permissions: string[];
}

Registered in config.entities[slug].hooks.

KeyTypeOperation
beforeCreateBeforeHookcreate
afterCreateAfterHookcreate
beforeUpdateBeforeHookupdate
afterUpdateAfterHookupdate
beforeDeleteBeforeHookdelete
afterDeleteAfterHookdelete
beforeReadBeforeHookread
afterReadAfterHookread
beforeListBeforeHooklist
afterListAfterHooklist

Registered in config.cart.hooks.

KeyTypeOperation
beforeAddItemBeforeHookaddItem
afterAddItemAfterHookaddItem
beforeRemoveItemBeforeHookremoveItem
afterRemoveItemAfterHookremoveItem
beforeUpdateQuantityBeforeHookupdate
afterUpdateQuantityAfterHookupdate

Registered in config.checkout.hooks or via plugin hooks with key prefix checkout..

KeyTypePipeline position
beforePaymentBeforeHook<CheckoutData>After calculateShipping, before validatePaymentMethod. Apply gift card deductions, loyalty redemptions, or other balance-based reductions here.
beforeCreateBeforeHook<CheckoutData>After authorizePayment, before order creation
afterCreateAfterHook<OrderResult>After order creation

Registered in config.orders.hooks.

KeyTypeOperation
beforeCreateBeforeHookcreate
afterCreateAfterHookcreate
beforeStatusChangeBeforeHookstatusChange
afterStatusChangeAfterHookstatusChange
beforeDeleteBeforeHookdelete

Registered in config.inventory.hooks.

KeyTypeOperation
afterAdjustAfterHookcustom

These are registered by the kernel in the appended slot at boot. They always run after user-defined and plugin hooks and cannot be disabled or reordered.

  • Webhook delivery — fires on 14 after-hooks; enqueues async delivery jobs
  • Audit logging — fires on 11 after-hooks; writes to commerce_audit_log
  • Search index sync — fires on catalog.afterCreate and catalog.afterUpdate
  • Order status email — fires on orders.afterStatusChange

Executed sequentially on CheckoutData:

StepFunctionPurpose
1validateCartNotEmptyLoads cart, verifies line items, enriches with entity title and type
2resolveCurrentPricesResolves live prices and computes subtotal
3checkInventoryAvailabilityVerifies sufficient stock for every line item
4applyPromotionCodesApplies automatic and code-based promotions
5calculateTaxComputes tax via the tax adapter
6calculateShippingComputes shipping cost
7Plugin checkout.beforePayment hooksGift cards, loyalty redemptions, balance-based reductions
8validatePaymentMethodAsserts a payment method ID is present
9authorizePaymentAuthorizes payment. Stores paymentIntentId in context.

After hooks (post-order creation, compensation chain)

Section titled “After hooks (post-order creation, compensation chain)”
StepFunctionCompensatablePurpose
1reserveInventoryStepYesReserves inventory. Released if a later step fails.
2capturePaymentStepYesCaptures the authorized payment. Reversed if a later step fails.
3initiateFulfillmentStepNo (best-effort)Triggers fulfillment
4sendConfirmationStepNo (best-effort)Sends order confirmation email

If the compensation chain fails, order status is set to cancelled.


Every hook key has three execution slots. When a hook fires, all handlers run sequentially:

prepended → configured → appended
SlotWritten byPurpose
prependedSystem internals, pluginsValidation guards, system checks — run before user hooks
configuredUser config (commerce.config.ts)Your hooks
appendedSystem internals, pluginsWebhook delivery, audit logging, search sync — run after user hooks

If any handler throws, execution stops and the error propagates.


Three mechanisms register hooks:

  1. Config hooks — directly in the config object under the relevant module key (e.g., config.checkout.hooks.beforeCreate)
  2. Entity hooks — per entity type in config.entities[slug].hooks
  3. Plugin hooks — via defineCommercePlugin({ hooks: () => [...] }), merged into config.hooks

See the Use Hooks guide for code examples of all three methods.