Extension API Reference
Complete API documentation for building NetPad extensions.
Core Interfacesโ
NetPadExtensionโ
The main interface that all extensions must implement.
interface NetPadExtension {
/** Extension metadata */
metadata: ExtensionMetadata;
/** Features provided by this extension */
features: ExtensionFeature[];
/** API routes (optional) */
routes?: ExtensionRoute[];
/** Request middleware (optional) */
middleware?: ExtensionMiddleware[];
/** Shared services (optional) */
services?: Record<string, unknown>;
/** React components (optional) */
components?: Record<string, React.ComponentType>;
/** Called when extension is loaded */
initialize?: () => Promise<void>;
/** Called when extension is unloaded */
cleanup?: () => Promise<void>;
}
ExtensionMetadataโ
Describes the extension for registration and display.
interface ExtensionMetadata {
/** Unique identifier (e.g., 'my-extension') */
id: string;
/** Human-readable name */
name: string;
/** Semantic version (e.g., '1.0.0') */
version: string;
/** Description of what the extension does */
description?: string;
/** Package author */
author?: string;
/** Documentation or homepage URL */
homepage?: string;
}
ExtensionRouteโ
Defines an API endpoint provided by the extension.
interface ExtensionRoute {
/** URL path (must start with /api/ext/{extension-name}/) */
path: string;
/** HTTP method */
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
/** Request handler function */
handler: RouteHandler;
/** Middleware IDs to apply to this route (optional) */
middleware?: string[];
}
/** Route handler function signature */
type RouteHandler = (
request: NextRequest,
context?: RouteContext
) => Promise<NextResponse>;
/** Context passed to route handlers */
interface RouteContext {
params?: Record<string, string | string[]>;
}
ExtensionMiddlewareโ
Defines middleware that processes requests.
interface ExtensionMiddleware {
/** Optional identifier for referencing this middleware */
id?: string;
/** URL pattern to match (supports wildcards) */
path: string;
/** Middleware handler function */
handler: MiddlewareHandler;
/** Execution priority (lower = earlier, default: 100) */
priority?: number;
}
/** Middleware handler function signature */
type MiddlewareHandler = (
request: NextRequest,
next: () => Promise<NextResponse>
) => Promise<NextResponse>;
ExtensionFeatureโ
Feature flags that extensions can provide.
type ExtensionFeature =
// Built-in features
| 'billing'
| 'stripe_integration'
| 'subscription_management'
| 'usage_tracking'
| 'atlas_provisioning'
| 'premium_support'
| 'advanced_analytics'
| 'sso_authentication'
| 'audit_logging'
| 'custom_branding'
// Custom features (must use this prefix)
| `custom:${string}`;
Registry Functionsโ
registerExtensionโ
Registers an extension with the system.
function registerExtension(extension: NetPadExtension): void
Parameters:
extension- The extension object to register
Example:
import { registerExtension } from '@/lib/extensions/registry';
registerExtension(myExtension);
getExtensionโ
Retrieves a registered extension by ID.
function getExtension(id: string): NetPadExtension | undefined
Parameters:
id- Extension ID (from metadata.id)
Returns: The extension object or undefined
Example:
const ext = getExtension('my-extension');
if (ext) {
console.log(ext.metadata.name);
}
isFeatureAvailableโ
Checks if a feature is available.
function isFeatureAvailable(feature: ExtensionFeature): boolean
Parameters:
feature- Feature identifier to check
Returns: true if any extension provides this feature
Example:
if (isFeatureAvailable('billing')) {
// Show billing UI
}
if (isFeatureAvailable('custom:my_feature')) {
// Show custom feature
}
getFeatureAvailabilityโ
Gets detailed information about a feature's availability.
function getFeatureAvailability(feature: ExtensionFeature): {
available: boolean;
provider?: string;
message?: string;
}
Parameters:
feature- Feature identifier to check
Returns: Object with availability details
Example:
const { available, provider, message } = getFeatureAvailability('billing');
if (!available) {
console.log(message); // "Feature requires extension that is not installed"
}
getExtensionRoutesโ
Returns all routes from all extensions.
function getExtensionRoutes(): ExtensionRoute[]
Returns: Array of all registered routes
Example:
const routes = getExtensionRoutes();
routes.forEach(route => {
console.log(`${route.method} ${route.path}`);
});
getExtensionMiddlewareโ
Returns all middleware from all extensions, sorted by priority.
function getExtensionMiddleware(): ExtensionMiddleware[]
Returns: Array of middleware sorted by priority (lower first)
Example:
const middleware = getExtensionMiddleware();
// Middleware is already sorted, first items run first
getServiceโ
Retrieves a service from an extension.
function getService<T>(extensionId: string, serviceName: string): T | undefined
Type Parameters:
T- Expected service type
Parameters:
extensionId- Extension IDserviceName- Service name (key in services object)
Returns: The service instance or undefined
Example:
interface MyService {
doSomething(): Promise<void>;
}
const service = getService<MyService>('my-extension', 'myService');
if (service) {
await service.doSomething();
}
getRegistryStatusโ
Returns current registry state for debugging.
function getRegistryStatus(): {
extensionCount: number;
extensions: Array<{
id: string;
name: string;
version: string;
features: string[];
routeCount: number;
}>;
features: string[];
initialized: boolean;
}
Returns: Registry status object
Example:
const status = getRegistryStatus();
console.log(`Loaded ${status.extensionCount} extensions`);
console.log(`Features: ${status.features.join(', ')}`);
initializeExtensionsโ
Initializes all registered extensions.
async function initializeExtensions(): Promise<void>
This is typically called automatically by the loader.
cleanupExtensionsโ
Calls cleanup on all extensions.
async function cleanupExtensions(): Promise<void>
resetRegistryโ
Resets the registry to initial state (for testing).
function resetRegistry(): void
Loader Functionsโ
loadExtensionsโ
Loads all available extensions.
async function loadExtensions(): Promise<void>
Behavior:
- Loads cloud extension if
NETPAD_CLOUD=true - Loads extensions from
NETPAD_EXTENSIONSenvironment variable - Initializes all loaded extensions
Example:
import { loadExtensions } from '@/lib/extensions/loader';
await loadExtensions();
extensionsLoadedโ
Checks if extensions have been loaded.
function extensionsLoaded(): boolean
Returns: true if loadExtensions() has been called
waitForExtensionsโ
Waits for extension loading to complete.
async function waitForExtensions(): Promise<void>
Example:
await waitForExtensions();
// Now safe to use extension features
registerExtensionManuallyโ
Registers an extension programmatically.
async function registerExtensionManually(extension: NetPadExtension): Promise<void>
Parameters:
extension- Extension to register
Example:
// For testing or dynamic registration
await registerExtensionManually(myTestExtension);
resetLoaderโ
Resets loader state (for testing).
function resetLoader(): void
Environment Variablesโ
NETPAD_CLOUDโ
Enables cloud mode and loads cloud extension.
NETPAD_CLOUD=true
NETPAD_EXTENSIONSโ
Comma-separated list of extension packages to load.
NETPAD_EXTENSIONS=@netpad/collaborate,@myorg/my-extension
NETPAD_SELF_HOSTEDโ
Indicates self-hosted deployment (disables cloud features).
NETPAD_SELF_HOSTED=true
Route Handler Patternsโ
Basic GET Handlerโ
async function handleGet(request: NextRequest): Promise<NextResponse> {
try {
const data = await fetchData();
return NextResponse.json({ success: true, data });
} catch (error) {
return NextResponse.json(
{ success: false, error: 'Failed to fetch data' },
{ status: 500 }
);
}
}
POST Handler with Body Parsingโ
async function handlePost(request: NextRequest): Promise<NextResponse> {
try {
const body = await request.json();
// Validate
if (!body.name) {
return NextResponse.json(
{ success: false, error: 'Name is required' },
{ status: 400 }
);
}
const result = await createData(body);
return NextResponse.json({ success: true, data: result });
} catch (error) {
return NextResponse.json(
{ success: false, error: 'Failed to create' },
{ status: 500 }
);
}
}
Handler with URL Parametersโ
async function handleGetById(
request: NextRequest,
context?: { params?: Record<string, string | string[]> }
): Promise<NextResponse> {
// Extract ID from URL
const url = new URL(request.url);
const pathParts = url.pathname.split('/');
const id = pathParts[pathParts.length - 1];
const data = await getById(id);
if (!data) {
return NextResponse.json(
{ success: false, error: 'Not found' },
{ status: 404 }
);
}
return NextResponse.json({ success: true, data });
}
Handler with Query Parametersโ
async function handleSearch(request: NextRequest): Promise<NextResponse> {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q') || '';
const limit = parseInt(searchParams.get('limit') || '10');
const offset = parseInt(searchParams.get('offset') || '0');
const results = await search(query, { limit, offset });
return NextResponse.json({
success: true,
results,
pagination: { limit, offset, total: results.total },
});
}
Middleware Patternsโ
Authentication Middlewareโ
const authMiddleware: ExtensionMiddleware = {
id: 'my-extension:auth',
path: '/api/ext/my-extension/*',
priority: 10,
handler: async (request, next) => {
const token = request.headers.get('authorization');
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Verify token...
const isValid = await verifyToken(token);
if (!isValid) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 }
);
}
return next();
},
};
Logging Middlewareโ
const loggingMiddleware: ExtensionMiddleware = {
id: 'my-extension:logging',
path: '/api/ext/my-extension/*',
priority: 1, // Run first
handler: async (request, next) => {
const start = Date.now();
console.log(`[${request.method}] ${request.url}`);
const response = await next();
console.log(`[${request.method}] ${request.url} - ${Date.now() - start}ms`);
return response;
},
};
Rate Limiting Middlewareโ
const rateLimits = new Map<string, { count: number; resetAt: number }>();
const rateLimitMiddleware: ExtensionMiddleware = {
id: 'my-extension:rate-limit',
path: '/api/ext/my-extension/*',
priority: 5,
handler: async (request, next) => {
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const now = Date.now();
const limit = rateLimits.get(ip);
if (limit && limit.resetAt > now && limit.count >= 100) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
if (!limit || limit.resetAt <= now) {
rateLimits.set(ip, { count: 1, resetAt: now + 60000 });
} else {
limit.count++;
}
return next();
},
};
Component Export Patternsโ
Basic Component Exportโ
// src/components/index.ts
export { MyComponent } from './MyComponent';
export type { MyComponentProps } from './MyComponent';
With Component IDsโ
// src/components/index.ts
export { MyComponent } from './MyComponent';
export type { MyComponentProps } from './MyComponent';
export const MY_EXTENSION_COMPONENTS = {
MY_COMPONENT: 'my-extension:my-component',
} as const;
export type MyExtensionComponentId =
(typeof MY_EXTENSION_COMPONENTS)[keyof typeof MY_EXTENSION_COMPONENTS];
Type Utilitiesโ
Type-Safe Service Accessโ
// Define service interface
interface MyService {
getData(): Promise<Data[]>;
createData(data: CreateData): Promise<Data>;
}
// Type-safe access
function getMyService(): MyService | undefined {
return getService<MyService>('my-extension', 'myService');
}
// Usage
const service = getMyService();
if (service) {
const data = await service.getData();
}
Extension Type Guardsโ
function isValidExtension(obj: unknown): obj is NetPadExtension {
if (!obj || typeof obj !== 'object') return false;
const ext = obj as Partial<NetPadExtension>;
return (
typeof ext.metadata?.id === 'string' &&
typeof ext.metadata?.name === 'string' &&
typeof ext.metadata?.version === 'string' &&
Array.isArray(ext.features)
);
}
Error Handlingโ
Standard Error Responseโ
interface ErrorResponse {
success: false;
error: string;
code?: string;
details?: unknown;
}
function errorResponse(
message: string,
status: number = 500,
code?: string
): NextResponse<ErrorResponse> {
return NextResponse.json(
{ success: false, error: message, code },
{ status }
);
}
Error Codesโ
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid authentication |
FORBIDDEN | 403 | Authenticated but not allowed |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid input data |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
FEATURE_UNAVAILABLE | 400 | Required feature not loaded |
Workflow Node Typesโ
WorkflowNodeDefinitionโ
Defines a custom workflow node for the palette and execution.
interface WorkflowNodeDefinition {
/** Unique node type identifier (e.g., 'custom:my-node') */
type: string;
/** Display label in palette */
label: string;
/** Description shown in tooltip */
description: string;
/** Category for grouping in palette */
category: NodeCategory;
/** Node color in hex format */
color: string;
/** MUI icon name */
icon: string;
/** Configuration fields for the node settings panel */
configFields?: NodeConfigField[];
/** Output handles (defaults to single 'output' handle) */
outputs?: NodeOutput[];
/** Whether this node supports multiple inputs */
multipleInputs?: boolean;
/** Extension that provides this node */
providedBy: string;
/** Version of the node definition */
version: string;
/** Documentation URL */
docsUrl?: string;
}
NodeConfigFieldโ
Defines a configuration field for node settings.
interface NodeConfigField {
/** Field name (key in config object) */
name: string;
/** Display label */
label: string;
/** Field type */
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'json' | 'connection' | 'expression';
/** Default value */
defaultValue?: unknown;
/** Placeholder text */
placeholder?: string;
/** Help text shown below field */
helpText?: string;
/** Whether field is required */
required?: boolean;
/** Options for select fields */
options?: Array<{ label: string; value: string }>;
/** Validation rules */
validation?: {
min?: number;
max?: number;
pattern?: string;
message?: string;
};
}
NodeOutputโ
Defines an output handle for a node.
interface NodeOutput {
/** Output handle ID */
id: string;
/** Display label */
label: string;
/** Description of what this output contains */
description?: string;
/** Whether this is the default/primary output */
primary?: boolean;
}
NodeHandlerโ
The function that executes when the node runs in a workflow.
type NodeHandler = (context: NodeExecutionContext) => Promise<NodeExecutionResult>;
NodeExecutionContextโ
Context passed to node handlers during execution.
interface NodeExecutionContext {
/** ID of the current node instance */
nodeId: string;
/** Type of the node */
nodeType: string;
/** Raw node configuration */
config: Record<string, unknown>;
/** Configuration with template variables resolved */
resolvedConfig: Record<string, unknown>;
/** Data from previous nodes */
inputs: Record<string, unknown>;
/** Information about the workflow trigger */
trigger: {
type: string;
payload?: Record<string, unknown>;
};
/** Get a decrypted connection from the vault */
getConnection: (vaultId: string) => Promise<{
connectionString: string;
database: string;
} | null>;
/** Get email credentials from the integration vault */
getEmailCredentials: (credentialId: string) => Promise<unknown>;
}
NodeExecutionResultโ
Result returned by a node handler.
interface NodeExecutionResult {
/** Whether the node executed successfully */
success: boolean;
/** Output data passed to downstream nodes */
data: Record<string, unknown>;
/** Error information if failed */
error?: {
code: string;
message: string;
retryable: boolean;
};
/** Execution metadata */
metadata?: {
durationMs?: number;
bytesProcessed?: number;
};
}
ExtensionWorkflowNodeโ
The structure for registering a workflow node in an extension.
interface ExtensionWorkflowNode {
/** Node definition for UI */
definition: Omit<WorkflowNodeDefinition, 'providedBy'>;
/** Node execution handler */
handler: NodeHandler;
}
Workflow Node Registry Functionsโ
registerExtensionNodeโ
Registers a custom workflow node from an extension.
function registerExtensionNode(
definition: WorkflowNodeDefinition,
handler: NodeHandler,
extensionId: string
): void
getExtensionNodeDefinitionsโ
Returns all extension-provided node definitions.
function getExtensionNodeDefinitions(): WorkflowNodeDefinition[]
getExtensionNodesByCategoryโ
Returns extension nodes filtered by category.
function getExtensionNodesByCategory(
category: NodeCategory
): WorkflowNodeDefinition[]
isExtensionNodeโ
Checks if a node type is provided by an extension.
function isExtensionNode(nodeType: string): boolean
Next Stepsโ
- Building Extensions - Step-by-step tutorial
- Example: Collaborate Extension - Real-world walkthrough
- Extension Architecture - System internals