@djangocfg/ext-base
Base utilities and common code for building DjangoCFG extensions.
Part of DjangoCFG — modern Django framework for production-ready SaaS applications.
What is this?
@djangocfg/ext-base is a foundation library for DjangoCFG extensions. It provides:
- Extension registration system - automatic extension metadata tracking
- Common React hooks - pagination, infinite scroll, data fetching patterns
- Environment utilities - isDevelopment, isProduction, isStaticBuild helpers
- Type-safe context helpers - factory functions for creating contexts
- Logger utilities - structured logging with tags
- Auth integration - convenient re-exports of useAuth
- TypeScript types - shared types for all extensions
This package is used internally by all official DjangoCFG extensions (newsletter, payments, support, leads, knowbase) and can be used to build your own custom extensions.
Install
pnpm add @djangocfg/ext-base
Quick Start
1. Create extension metadata
import type { ExtensionMetadata } from '@djangocfg/ext-base';
export const extensionConfig: ExtensionMetadata = {
name: 'my-extension',
version: '1.0.0',
author: 'Your Name',
displayName: 'My Extension',
description: 'Amazing extension functionality',
icon: '🚀',
license: 'MIT',
keywords: ['extension', 'feature'],
};
2. Create your extension provider
'use client';
import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
import { extensionConfig } from '../config';
export function MyExtensionProvider({ children }) {
return (
<ExtensionProvider metadata={extensionConfig}>
{/* Your extension contexts here */}
{children}
</ExtensionProvider>
);
}
3. Use in your app
import { MyExtensionProvider } from '@your-org/my-extension/hooks';
export default function RootLayout({ children }) {
return (
<MyExtensionProvider>
{children}
</MyExtensionProvider>
);
}
The extension will automatically register itself and log in development mode:
[ext-base] Extension registered: My Extension v1.0.0
Core Features
Environment Configuration
import { isDevelopment, isProduction, isStaticBuild, isClient, isServer } from '@djangocfg/ext-base';
if (isDevelopment) {
console.log('Running in development mode');
}
if (isStaticBuild) {
}
API Factory
Create extension API instances with shared authentication in one line:
import { API } from './generated/ext_myextension';
import { createExtensionAPI } from '@djangocfg/ext-base/api';
export const apiMyExtension = createExtensionAPI(API);
Before (manual setup):
import { API } from './generated/ext_myextension';
import { api as accountsApi } from '@djangocfg/api';
const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
const apiUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_API_URL || '';
const storage = (accountsApi as any)._storage;
export const apiMyExtension = new API(apiUrl, { storage });
After (with factory):
import { API } from './generated/ext_myextension';
import { createExtensionAPI } from '@djangocfg/ext-base/api';
export const apiMyExtension = createExtensionAPI(API);
import { usePagination, useInfinitePagination } from '@djangocfg/ext-base/hooks';
const { items, page, pageSize, totalPages, goToPage, nextPage, prevPage } = usePagination({
keyPrefix: 'articles',
fetcher: async (page, pageSize) => {
const response = await api.articles.list({ page, page_size: pageSize });
return response.data;
},
initialPage: 1,
pageSize: 20,
});
const { items, isLoading, hasMore, loadMore } = useInfinitePagination({
keyPrefix: 'articles',
fetcher: async (page, pageSize) => {
const response = await api.articles.list({ page, page_size: pageSize });
return response.data;
},
pageSize: 20,
});
Type-Safe Context Creation
import { createExtensionContext } from '@djangocfg/ext-base/hooks';
interface MyContextValue {
data: any[];
refresh: () => void;
}
const { Provider, useContext: useMyContext } = createExtensionContext<MyContextValue>({
displayName: 'MyContext',
errorMessage: 'useMyContext must be used within MyProvider',
});
export function MyComponent() {
const { data, refresh } = useMyContext();
return <div>{data.length} items</div>;
}
Logger
import { createExtensionLogger } from '@djangocfg/ext-base';
const logger = createExtensionLogger({
tag: 'my-extension',
level: 'info',
});
logger.info('Extension initialized');
logger.error('Something went wrong', error);
logger.success('Operation completed!');
Auth Integration
import { useAuth } from '@djangocfg/ext-base/auth';
function MyComponent() {
const { user, isAuthenticated, login, logout } = useAuth();
if (!isAuthenticated) {
return <button onClick={login}>Login</button>;
}
return <div>Welcome, {user?.email}</div>;
}
Error Handling
import { handleError, formatError } from '@djangocfg/ext-base';
try {
await someOperation();
} catch (error) {
handleError(error, (formattedError) => {
logger.error('Operation failed:', formattedError);
});
}
Package Exports
Main entry (@djangocfg/ext-base)
Server-safe exports - can be used in both client and server components:
- All TypeScript types
- Environment configuration (isDevelopment, isProduction, etc.)
- API factory (createExtensionAPI)
- Logger utilities
- Error handling utilities
Hooks entry (@djangocfg/ext-base/hooks)
Client-only exports:
ExtensionProvider - main provider component
usePagination - standard pagination hook
useInfinitePagination - infinite scroll hook
createExtensionContext - context factory
Auth entry (@djangocfg/ext-base/auth)
Auth re-exports:
useAuth - authentication hook
UserProfile type
AuthContextType type
API entry (@djangocfg/ext-base/api)
API utilities:
createExtensionAPI - API instance factory
getSharedAuthStorage - Get shared auth storage
TypeScript Types
import type {
ExtensionMetadata,
ExtensionProviderProps,
PaginatedResponse,
PaginationParams,
PaginationState,
InfinitePaginationReturn,
ExtensionLogger,
LoggerOptions,
ExtensionError,
ErrorHandler,
ExtensionContextOptions,
} from '@djangocfg/ext-base';
Example: Complete Extension
export const extensionConfig: ExtensionMetadata = {
name: 'blog',
version: '1.0.0',
author: 'Your Company',
displayName: 'Blog',
description: 'Blog management system',
icon: '📝',
keywords: ['blog', 'articles'],
};
'use client';
import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
import { useInfinitePagination } from '@djangocfg/ext-base/hooks';
import { createExtensionLogger } from '@djangocfg/ext-base';
import { extensionConfig } from '../config';
const logger = createExtensionLogger({ tag: 'blog' });
export function BlogProvider({ children }) {
const { items, loadMore, hasMore } = useInfinitePagination({
keyPrefix: 'blog-articles',
fetcher: async (page, pageSize) => {
const response = await api.articles.list({ page, page_size: pageSize });
return response.data;
},
});
logger.info('Blog provider initialized with', items.length, 'articles');
return (
<ExtensionProvider metadata={extensionConfig}>
{/* Your contexts here */}
{children}
</ExtensionProvider>
);
}
Best Practices
- Always use ExtensionProvider - Wrap your extension with it for proper registration
- Define metadata - Create a
config.ts file with complete metadata
- Use provided hooks - Leverage
usePagination and useInfinitePagination
- Structured logging - Use
createExtensionLogger with consistent tags
- Type safety - Import types from
@djangocfg/ext-base
- Separate entry points - Use
/hooks for client code, main entry for server-safe code
License
MIT
Links