Apps
Apps can extend certain core features of the Swell platform by hooking into native events and endpoints. A Swell App Extension is defined by its ability to respond to core events using functions, or its ability to tap into layers of the platform that serve distinct purposes such as these.
- Payment gateways: Implement gateways using hooks to vault customer information, charge, and refund payments.
- Shipping services: Implement shipping services with dynamic carrier-based pricing or custom logic.
- Tax calculation: Integrate with various tax calculation services.
App extensions are configured in swell.json and referenced by functions and client-side components. This allows you to configure multiple extensions in a single app.
{
"id": "example_app",
"type": "integration",
"extensions": [
{
"id": "example",
"type": "payment",
"name": "My Payment Service"
}
]
}Extensions can be enabled when an app is installed, which causes their corresponding functions to be triggered by events. Functions can listen to any valid event, however there are specific events most relevant to extensions, as described later in this document.
New extension types and standard events may be introduced over time. The following extension types are currently available.
Each event described here is considered an event hook, which means the event is is only called synchronously during the processing of a record. By default, they are called before the event occurs. This differs from regular model events which are called asynchronously by default.
Response properties listed below follow the corresponding standard Swell models.
Payment extensions are for integrating payment gateways, either with 3rd-party services or custom logic.
Payment events:
- payment.charge – Triggered before a payment is created. Receives a payment record.
- Response properties:
- success – Boolean true or false to indicate whether the payment was successful.
- transaction_id – String typically from an external payment gateway to identify the payment transaction.
- Response properties:
- payment.refund – Triggered before a payment is refunded. Receives a payment record.
- Response properties:
- success – Boolean true or false to indicate whether the refund was successful.
- transaction_id – String typically from an external payment gateway to identify the refund transaction.
- Response properties:
export const config: SwellConfig = {
extension: "example",
description: "Charge a credit card",
model: {
events: ["payment.charge"]
},
};
export default async function (req: SwellRequest) {
// ... perform charge logic
return {
success: true,
transaction_id: '...',
}
}
Shipping extensions are for integrating shipment rating and price calculation, either with 3rd-party services or custom logic.
Shipping events:
- order.shipping
- Response properties:
- shipment_rating – Shipment rating object containing services and their prices, or errors if any occurred.
- Response properties:
export const config: SwellConfig = {
extension: "example",
description: "Calculate shipping services",
model: {
events: ["order.shipping"]
},
};
export default async function (req: SwellRequest) {
// ... perform shipping calculation
return {
shipment_rating: {
services: [
{
id: '...',
price: 9.99
}
],
errors: [...], // optional
}
}
}
Tax extensions are for integrating tax calculation, either with 3rd-party services or custom logic.
Tax events:
- order.taxes
- Response properties:
- taxes – Tax array containing amounts calculated for the order.
- items.taxes – Optional item-level tax details.
- Response properties:
export const config: SwellConfig = {
extension: "example",
description: "Calculate taxes",
model: {
events: ["order.taxes"]
},
};
export default async function (req: SwellRequest) {
// ... perform tax calculation
return {
taxes: [
{
id: '...',
amount: 10
}
],
items: [ // optional
{
id: '...',
taxes: [...]
}
],
}
}
In addition to server-side functions, an extension can include client-side components that render inside Swell checkout. A payment extension uses a component to display the gateway's payment UI, tokenize the customer's card, and save the result on the cart. Components can also render fields in the Swell dashboard. See UI components.
Components are Preact components placed at the root of the app's components/ folder as .jsx or .tsx files, and are pushed to Swell along with the rest of the app configuration. A checkout component names the extension it implements with extension: { id } in its config, and adds targets: [{ id: "checkout.payment.methods" }] to appear in Swell's hosted checkout. The CLI reads the config without running the file, so it must be an object literal with literal values:
import { useEffect, useRef } from 'preact/hooks';
import type {
ComponentProps,
PaymentContext,
PaymentSubmitResult,
} from '@swell/apps-sdk/components';
export const config = {
description: 'Example gateway checkout',
extension: { id: 'card' },
targets: [{ id: 'checkout.payment.methods' }],
};
declare const window: Window & { Gateway?: any };
function loadGatewaySdk(): Promise<void> {
if (window.Gateway) return Promise.resolve();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://example.com/gateway-sdk.js';
script.onload = () => resolve();
script.onerror = () => reject(new Error('The gateway could not be loaded'));
document.head.appendChild(script);
});
}
export default function Gateway({
context,
settings,
on,
call,
}: ComponentProps<never, PaymentContext>) {
// The submit handler is registered once, so it reads the latest cart from a ref
const latest = useRef(context);
latest.current = context;
const publicKey = (settings.card as { public_key?: string } | undefined)?.public_key;
useEffect(() => {
void loadGatewaySdk().catch(() => {});
return on('submit', async (): Promise<PaymentSubmitResult> => {
const { cart } = latest.current;
await loadGatewaySdk();
// Runs the extension's payment.create_intent function
const intent = await call<{ id: string }>('createIntent', {
amount: cart.capture_total,
currency: cart.currency,
});
// Open the gateway's payment UI and tokenize the card
const response = await window.Gateway.open({ publicKey, intentId: intent.id });
// Runs the extension's payment.get_intent function to verify the result
const payment = await call<{ id: string; token: string; last4: string; brand: string }>(
'getIntent',
response,
);
// Merged into the cart's billing before checkout submits the order
return {
billing: {
card: { token: payment.token, last4: payment.last4, brand: payment.brand },
intent: { gateway: { id: payment.id } },
},
};
});
}, [on, call, publicKey]);
return <p style={{ margin: 0 }}>You will enter your card details in a secure window.</p>;
}
Checkout components need Swell CLI 2.9.28 or later and @swell/apps-sdk 2.0.0-alpha.7 or later, published on the npm tag next. Import only the types from @swell/apps-sdk/components. The component runs in a frame on the app installation's own domain, like UI components, so a modal the gateway adds to document.body, such as a 3-D Secure challenge, covers the checkout while it's open. Render visible content before opening a modal, so the customer sees the payment method.
When the customer selects the extension's payment method, Swell's checkout renders the component and passes it these props:
- context: the cart, with account_id, currency and capture_total, and the selected method. Type it with PaymentContext.
- settings: the app's settings fields that set public: true, grouped by settings file, such as settings.card.public_key.
- on('submit', handler): runs when the customer places the order. Return { billing }, which checkout merges into the cart's billing before it submits the order. Throw an error to stop the order and show its message.
- call('createIntent', data) and call('getIntent', data): run the extension's payment.create_intent and payment.get_intent functions, which receive data as req.data.intent, and resolve with their result.
- on('redirect', handler): runs when the customer comes back to checkout from a redirect, with the URL parameters in params. Return { billing } to complete the payment, or nothing to ignore the redirect.
- setValidity(message): keeps the customer from placing the order and shows the message, until it's called with null.
If the submit handler returns no billing, checkout shows "The payment method did not respond" and doesn't submit the order.
Checkout components used to have a string extension and receive SwellData props from swell-js:
export const config: SwellConfig = {
extension: "card",
description: "Example gateway client side integration",
};
export default function GatewayComponent({ registerHandlers, createIntent, getIntent, updateCart, onReady }: SwellData) {
// ...
}Since October 15, 2026, Swell's hosted checkout renders payment methods only with @swell/apps-sdk, so it doesn't show components in this format. Custom checkouts that use swell-js still render them, and only them. To support Swell's checkout, update the component:
- Change extension: "card" to extension: { id: "card" }, and add targets: [{ id: "checkout.payment.methods" }].
- Type the props as ComponentProps<never, PaymentContext> from @swell/apps-sdk/components instead of SwellData.
- Register the submit handler with on('submit', handler) in an effect, instead of registerHandlers({ onSubmit }). Read the cart from context.cart, and return { billing } instead of calling updateCart.
- Replace createIntent(data) and getIntent(data) with call('createIntent', data) and call('getIntent', data). The app's payment.create_intent and payment.get_intent functions don't change.
- Load the gateway's browser SDK with a script element instead of loadLib, and remove onReady: the component is ready once it renders.
To support both Swell's checkout and a custom swell-js checkout, keep the swell-js component and add a second component in the new format for the same extension.
For complete working examples, see the Build a payment gateway, Build a shipping service, and Build a tax integration tutorials, each with full source code on GitHub.