@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 provides the foundation for DjangoCFG extensions with:
- Extension registration system and metadata tracking
- React hooks for pagination, infinite scroll, and data fetching
- Environment utilities (isDevelopment, isProduction, isStaticBuild)
- Type-safe context helpers and logger utilities
- Auth integration and API factory
- CLI tool for managing extensions
Used internally by official extensions (newsletter, payments, support, leads, knowbase) and for building custom extensions.
Install
pnpm add @djangocfg/ext-base
CLI Usage
The package includes a CLI tool for creating new DjangoCFG extensions:
djangocfg-ext create
djangocfg-ext test
djangocfg-ext list
djangocfg-ext info <extension-name>
djangocfg-ext help
The CLI will guide you through creating a new extension with proper structure and configuration using the createExtensionConfig helper.
Features:
- Interactive prompts for package name, description, icon, and category
- Automatic npm registry validation to prevent duplicate package names
- Support for both scoped (
@my-org/my-extension) and unscoped (my-extension) package names
- Template generation with proper placeholder replacement
- Playground environment with Next.js 15 for rapid development
- Auto-dependency installation and type checking
- Smart workspace detection for monorepos
Quick Start
1. Create extension metadata
Use the createExtensionConfig helper to automatically pull data from package.json:
import { createExtensionConfig } from '@djangocfg/ext-base';
import packageJson from '../package.json';
export const extensionConfig = createExtensionConfig(packageJson, {
name: 'my-extension',
displayName: 'My Extension',
icon: 'Rocket',
category: 'utilities',
features: [
'Feature 1',
'Feature 2',
'Feature 3',
],
minVersion: '2.0.0',
examples: [
{
title: 'Basic Usage',
description: 'How to use this extension',
code: `import { MyComponent } from '@your-org/my-extension';`,
language: 'tsx',
},
],
});
This automatically imports from package.json:
- version
- author
- description
- license
- homepage
- githubUrl (from repository)
- keywords
- peerDependencies
2. Create extension provider
'use client';
import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
import { extensionConfig } from '../config';
export function MyExtensionProvider({ children }) {
return (
<ExtensionProvider metadata={extensionConfig}>
{children}
</ExtensionProvider>
);
}
3. Use in your app
import { MyExtensionProvider } from '@your-org/my-extension';
export default function RootLayout({ children }) {
return (
<MyExtensionProvider>
{children}
</MyExtensionProvider>
);
}
Development Workflow
Every extension created with the CLI includes a playground - a Next.js 15 development environment for rapid testing and iteration.
Starting the Playground
cd your-extension
pnpm dev:playground
This command:
- ✅ Automatically builds your extension
- ✅ Starts Next.js dev server on port 3333
- ✅ Opens browser automatically
- ✅ Hot-reloads on source changes
Playground Features
- Next.js 15 App Router with React 19
- Tailwind CSS v4 pre-configured
- BaseApp wrapper with theme, auth, and SWR
- Auto-build extension before starting (via
predev script)
- Auto-open browser when dev server is ready
- Hot reload for instant feedback
Building for Production
pnpm build
pnpm build:playground
pnpm check
Smart Provider Pattern
Extensions support a Smart Provider Pattern that allows components to work both standalone and with manual provider wrapping.
Using withSmartProvider
Create self-contained components that automatically wrap themselves with the provider when needed:
import { withSmartProvider } from '@your-org/my-extension';
function MyComponent() {
const { data } = useMyExtension();
return <div>{data}</div>;
}
export const MySmartComponent = withSmartProvider(MyComponent);
Usage:
<MySmartComponent />
<MyExtensionProvider>
<MySmartComponent />
<MySmartComponent />
</MyExtensionProvider>
Benefits:
- ✅ Components work out-of-the-box without provider boilerplate
- ✅ Multiple components can still share context when manually wrapped
- ✅ Best of both worlds - simplicity and flexibility
When to use:
- Library components that should "just work"
- Components that might be used in isolation
- Reducing boilerplate for simple use cases
When to use manual provider:
- Multiple components need to share state
- Performance optimization (single provider instance)
- Explicit context boundaries
Core Features
Extension Config Helper
The createExtensionConfig helper creates a typed extension configuration by combining package.json data with manual metadata:
import { createExtensionConfig, type ExtensionConfigInput } from '@djangocfg/ext-base';
import packageJson from '../package.json';
export const extensionConfig = createExtensionConfig(packageJson, {
name: 'my-extension',
displayName: 'My Extension',
icon: 'Package',
category: 'utilities',
features: ['Feature list for marketplace'],
minVersion: '2.0.0',
githubStars: 100,
examples: [
{
title: 'Example title',
description: 'Example description',
code: 'import { Component } from "@your-org/my-extension";',
language: 'tsx',
},
],
});
Automatically imported from package.json:
version - Package version
author - Author name (from string or object)
description - Package description
license - License type
homepage - Homepage URL
githubUrl - Repository URL
keywords - Keywords array
peerDependencies - Peer dependencies (workspace:* auto-replaced with latest)
packageDependencies - Dependencies from package.json (workspace:* auto-replaced with latest)
Auto-generated:
npmUrl - npm package URL (https://www.npmjs.com/package/[name])
marketplaceId - URL-safe ID (@ and / replaced with -), e.g. @djangocfg/ext-newsletter → djangocfg-ext-newsletter
marketplaceUrl - Marketplace URL (only for official @djangocfg extensions): https://hub.djangocfg.com/extensions/[marketplaceId]
installCommand - pnpm install command
downloadUrl - npm tarball download URL
preview - Preview image URL (https://unpkg.com/[name]@latest/preview.png)
tags - Same as keywords
Note: All workspace:* dependencies are automatically replaced with latest for marketplace display.
Environment Configuration
import { isDevelopment, isProduction, isStaticBuild } from '@djangocfg/ext-base';
if (isDevelopment) {
console.log('Running in development mode');
}
API Factory
Create extension API instances with automatic configuration:
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, totalPages, goToPage, nextPage, prevPage } = usePagination({
keyPrefix: 'articles',
fetcher: async (page, pageSize) => {
const response = await api.articles.list({ page, page_size: pageSize });
return response.data;
},
pageSize: 20,
});
const { items, isLoading, hasMore, loadMore } = useInfinitePagination({
keyPrefix: 'articles',
fetcher: async (page, pageSize) => api.articles.list({ page, page_size: pageSize }).then(r => r.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',
});
Logger
import { createExtensionLogger } from '@djangocfg/ext-base';
const logger = createExtensionLogger({ tag: 'my-extension' });
logger.info('Extension initialized');
logger.error('Operation failed', error);
logger.success('Completed!');
Auth Integration
import { useAuth } from '@djangocfg/ext-base/auth';
function MyComponent() {
const { user, isAuthenticated, login, logout } = useAuth();
return isAuthenticated ? (
<div>Welcome, {user?.email}</div>
) : (
<button onClick={login}>Login</button>
);
}
Package Exports
@djangocfg/ext-base | Server-safe exports (types, environment, API factory, logger, error handling) | Server & client components |
@djangocfg/ext-base/hooks | Client-only exports (ExtensionProvider, pagination hooks, context factory) | Client components only |
@djangocfg/ext-base/auth | Auth re-exports (useAuth, types) | Client components only |
@djangocfg/ext-base/api | API utilities (createExtensionAPI, getSharedAuthStorage) | Server & client components |
For your own extensions:
Extension templates automatically export:
extensionConfig - Extension metadata
[Name]Provider - Main provider component (wraps ExtensionProvider)
use[Name] - Context hook (required provider)
use[Name]Optional - Context hook (optional provider)
withSmartProvider - HOC for self-contained components
Server-safe imports:
- Use
@your-org/ext-name/config to import extension metadata without loading React components
- This is recommended for server components and build tools
Extension Categories
The package exports a standardized list of extension categories:
import { EXTENSION_CATEGORIES } from '@djangocfg/ext-base';
EXTENSION_CATEGORIES.forEach(category => {
console.log(category.title, category.value);
});
Available categories:
forms - Forms, CRM, Lead Management
payments - Payment Processing, Billing
content - Content Management, Marketing
support - Support, Helpdesk, Tickets
utilities - Tools, Base, Infrastructure
analytics - Analytics, Tracking, Monitoring
security - Security, Authentication, Authorization
integration - Third-party Integrations
other - Other/Uncategorized
TypeScript Types
import type {
ExtensionMetadata,
ExtensionConfigInput,
ExtensionCategory,
ExtensionExample,
ExtensionProviderProps,
PaginatedResponse,
PaginationParams,
PaginationState,
InfinitePaginationReturn,
ExtensionLogger,
ExtensionError,
} from '@djangocfg/ext-base';
Best Practices
Configuration & Metadata
- Use
createExtensionConfig helper to maintain Single Source of Truth from package.json
- Use Lucide icon names (not emoji) for
icon field
- Include comprehensive
features list for marketplace visibility
- Provide code
examples with proper syntax highlighting
- Include a
preview.png file in your extension root (1200x630px recommended)
- List
preview.png in your package.json files array for npm publication
Development
- Use the playground for rapid development (
pnpm dev:playground)
- Test your components in isolation before integrating
- Run type checks before building (
pnpm check)
- Use
createExtensionLogger with consistent tags for structured logging
Architecture
- Always wrap your extension with
ExtensionProvider for proper registration
- Export main provider as
[Name]Provider (not [Name]ExtensionProvider)
- Use
withSmartProvider for components that should work standalone
- Prefer manual provider wrapping when components need shared state
- Use provided pagination hooks for consistent data fetching
- Separate client-only code appropriately
Publishing
- Test with
pnpm build before publishing
- Verify preview image is accessible via unpkg
- Ensure all peer dependencies are correctly listed
- Include clear usage examples in README
License
MIT
Links