Extending the SDK
The Haus Storefront SDK is intentionally open-ended. This guide walks through three patterns for extending the generated GraphQL client and delivering project-specific behaviour without forking packages.
Prerequisites
- You have the
DataProviderrunning with the Vendure provider (seeGetting Started).- You understand the base API documented in
Core.
1. Compose Custom Methods Around useSdk
The quickest way to wrap SDK operations is to co-locate domain helpers with your feature modules.
// lib/orders.ts
import { useSdk } from '@haus-storefront-react/core'
export function useExtendedOrders() {
const sdk = useSdk()
async function fetchActiveOrderWithPayments() {
const [order, payments] = await Promise.all([
sdk.activeOrder(),
sdk.payments({ take: 5 }),
])
return {
...order,
recentPayments: payments.items,
}
}
return {
fetchActiveOrderWithPayments,
}
}
Pair the helper with React Query (or Remix loaders) to ensure consistent caching:
import { useQuery } from '@haus-storefront-react/core'
import { useExtendedOrders } from '@/lib/orders'
export function PaymentAwareCart() {
const { fetchActiveOrderWithPayments } = useExtendedOrders()
const { data, isLoading, error } = useQuery({
queryKey: ['active-order', 'payments'],
queryFn: fetchActiveOrderWithPayments,
})
if (isLoading) return <span>Loading…</span>
if (error) return <span role='alert'>{error.message}</span>
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
This keeps project-specific orchestration separate from the generated client while retaining type safety from the SDK packages.
2. Inject Plugin Configs to Extend Requests
DataProvider accepts a pluginConfigs array. Each entry can extend the SDK with new request methods, GraphQL query updates, settings, and feature toggles (Vendure Plugin Configs).
import { DataProvider } from '@haus-storefront-react/core'
import { VendureBadgePlugin } from '@haus-storefront-react/vendure-plugin-configs/badge'
const badgePlugin = VendureBadgePlugin.init({
enableFeatures: {
imageBadges: true,
textBadges: true,
},
})
export function AppShell({ children }) {
return (
<DataProvider
provider='vendure'
platform='web'
options={{
apiUrl: process.env.NEXT_PUBLIC_VENDURE_SHOP_API!,
vendureToken: process.env.NEXT_PUBLIC_VENDURE_CHANNEL_TOKEN!,
pluginConfigs: [badgePlugin],
}}
>
{children}
</DataProvider>
)
}
Within the plugin definition you can attach new request methods that become available on the SDK instance:
import { VendurePluginConfig } from '@haus-storefront-react/vendure-plugin-configs'
export class CampaignPlugin extends VendurePluginConfig {
constructor() {
super({
name: 'campaign',
enabled: true,
queryUpdates: {
Product: {
fields: {
campaignAssignments: {
selection: 'id name startsAt endsAt',
},
},
},
},
requests: {
async campaignAssignments(productId: string) {
const sdk = this.getSdk()
const result = await sdk.client.query({
document: /* GraphQL */ `
query CampaignAssignments($id: ID!) {
campaignAssignments(productId: $id) {
id
name
startsAt
endsAt
}
}
`,
variables: { id: productId },
})
return result.campaignAssignments
},
},
})
}
}
export const campaignPlugin = new CampaignPlugin().init({
enableFeatures: {
campaignAssignments: true,
},
})
Once registered, call campaignPlugin.getRequests() anywhere inside your app (or re-export the helpers) to reach the custom methods. Every request receives the same SDK instance that powers the core hooks:
const { campaignAssignments } = campaignPlugin.getRequests()
const assignments = await campaignAssignments(variant.productId)
Tip: Keep plugin instances outside of React components (for example, in a configuration module) so they preserve state across renders and can be shared between SSR/edge contexts. Passing the same instance to
DataProviderand to feature modules keepsgetRequests()typed and in sync.
3. Tap into Transport Hooks
For cross-cutting behaviour—tracing, auth headers, or localized content—use the Axios strategies exposed on the provider options.
<DataProvider
provider='vendure'
platform='web'
options={{
apiUrl: process.env.NEXT_PUBLIC_VENDURE_SHOP_API!,
vendureToken: process.env.NEXT_PUBLIC_VENDURE_CHANNEL_TOKEN!,
requestInterceptorStrategy: async (config, defaultHandler) => {
config.headers = config.headers ?? {}
config.headers['x-haus-trace'] = window.__TRACE_ID__
await defaultHandler(config)
},
responseInterceptorStrategy: async (response, defaultHandler) => {
if (response.headers['x-cache-status'] === 'stale') {
const queryClient = window.queryClient
await queryClient?.invalidateQueries({ queryKey: ['products'] })
}
await defaultHandler(response)
},
}}
>
<App />
</DataProvider>
Because the SDK shares a single Axios instance per provider, these interceptors run for GraphQL queries, mutations, and custom requests. They are ideal for injecting correlation IDs, handling transient auth errors, or dynamically toggling preview features.
4. Version and Package Your Extensions
- Keep plugin configs typed: Export the generics you pass into
VendurePluginConfigso downstream teams inherit the augmented types. - Ship helpers as internal npm packages: Bundling your
useExtendedOrders-style utilities prevents drift across storefronts. - Document enablement: Storefront teams should know which
pluginConfigsbelong in each environment (production vs. preview) and how to toggle features viainit({ enableFeatures }).