Skip to Content
FrontendRecipesExtensions

Extensions

Six @djangocfg/ext-* packages provide drop-in features that follow identical patterns.

For the generated API layer that extensions build on see Data Layer. For Context architecture see State Management.

Available Extensions

ExtensionPurposeKey Exports
ext-baseFoundation for all extensionsExtensionProvider, createExtensionContext, createExtensionAPI
ext-knowbaseKnowledge base + AI chat RAGKnowbaseChatProvider, KnowledgeChat, ChatWidget
ext-leadsContact forms / CRMContactForm, ContactPage, ContactFormProvider
ext-newsletterEmail subscriptionsHeroProps, newsletter API
ext-paymentsCrypto/fiat walletWalletPage, WalletProvider, useBalance, useActivity
ext-supportSupport tickets / helpdeskSupportPage, SupportProvider, useTickets

Standard Extension Structure

All six extensions follow this package layout:

@djangocfg/ext-<name>/ ├── src/ │ ├── index.ts # Server-safe exports (types, API, config) │ ├── hooks.ts # Client-only exports (providers, SWR hooks, components) │ ├── api/ │ │ └── generated/ # Auto-generated from Django OpenAPI │ │ └── ext_<name>/ │ │ ├── _utils/fetchers/ │ │ ├── _utils/hooks/ │ │ └── _utils/schemas/ │ ├── contexts/ # React context providers │ ├── components/ # UI components │ └── adapters/ # API vs Demo adapters (ext-payments, ext-support) ├── package.json └── tsup.config.ts # ESM + CJS dual build

Server-safe exports go through index.ts, client-only exports through hooks.ts.

Integration Pattern

// In Next.js layout or page import { WalletProvider } from '@djangocfg/ext-payments'; import { WalletPage } from '@djangocfg/ext-payments'; export default function PaymentsPage() { return ( <WalletProvider> <WalletPage /> </WalletProvider> ); } // Demo mode (no backend needed) <WalletPage isDemo />

Shared Auth via ext-base

Extensions share auth storage with the main @djangocfg/api client:

import { createExtensionAPI } from '@djangocfg/ext-base'; import { getSharedAuthStorage } from '@djangocfg/ext-base/api'; // Extension API automatically uses the same JWT tokens const extensionApi = createExtensionAPI({ baseUrl: process.env.NEXT_PUBLIC_API_URL, storage: getSharedAuthStorage(), });

This means users authenticate once with the main app and all extensions share that session.

Adapter Pattern

ext-payments and ext-support support both live API and demo mode via adapters:

// adapters/api.ts — real backend export class APIAdapter implements WalletAdapter { async getBalance(): Promise<BalanceResult> { return await api.ext_payments.balanceList(); } } // adapters/demo.ts — mock data export class DemoAdapter implements WalletAdapter { async getBalance(): Promise<BalanceResult> { return { balance: 1250.00, currency: 'USD' }; } } // Context picks adapter based on isDemo prop const adapter = isDemo ? new DemoAdapter() : new APIAdapter();

The adapter interface ensures both implementations expose the same methods. New environments (test, staging) can provide their own adapter.

Self-Contained Mini-Apps

Extensions can be deployed as fully self-contained modules within an app:

private/payments/src/ ├── api/generated/ext_payments/ # Own generated API ├── contexts/WalletContext.tsx # Own context ├── contexts/types.ts # Own type re-exports ├── components/ # Own components └── hooks/ # Own hooks

Does not share _lib/api/ or _lib/contexts/ with the parent app. Useful for features backed by separate Django apps.

Data Merging with useMemo

Extensions that aggregate multiple data sources use useMemo for merging:

const activity = useMemo(() => { const items: ActivityItem[] = [ ...payments.map(p => ({ ...p, activityType: 'payment' as const })), ...transactions.map(t => ({ ...t, activityType: 'transaction' as const })), ...withdrawals.map(w => ({ ...w, activityType: 'withdrawal' as const })), ]; return items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); }, [payments, transactions, withdrawals]);

Rules

  1. Import server-safe exports from @djangocfg/ext-<name>, client-only exports from @djangocfg/ext-<name>/hooks
  2. Use createExtensionAPI with getSharedAuthStorage() to share auth with the main app
  3. Use the adapter pattern for extensions that need demo mode — define an interface, implement API and Demo variants
  4. Extension translations register via createRequestConfig with a dedicated namespace
  5. Self-contained extension modules have their own generated/, contexts/, types.ts — they do not share the parent app’s _lib/
Last updated on