Plugin Development
Learn how to create custom plugins for Better Billing
Plugin Development
Better Billing's power comes from its plugin system. Plugins allow you to extend functionality, add new payment providers, create custom schemas, and build API endpoints.
Plugin Architecture
Every plugin is created using the createPlugin factory function with two key components:
import { createPlugin } from "better-billing";
const myPlugin = createPlugin(
(deps) => {
// Plugin implementation
return {
schema?: { ... }, // Database schema definitions
providers?: [...], // Payment provider implementations
endpoints?: { ... }, // API endpoint definitions
};
},
{
dependsOn: [otherPlugin] as const // Plugin dependencies
}
);What Plugins Can Do
- Define Schemas: Create or extend database tables
- Implement Providers: Add payment processors or custom capabilities
- Create API Endpoints: Handle webhooks, custom logic, or integrations
- Extend Existing Functionality: Modify or enhance other plugins
Plugin Types
Core Plugin
- Purpose: Provides foundational schema and core billing concepts
- Dependencies: None (base plugin)
- Exports: Essential database schemas (subscriptions, customers, etc.)
- Required: Every Better Billing setup must include the core plugin
Provider Plugins
- Purpose: Implement specific payment provider integrations
- Dependencies: Typically depends on core plugin
- Examples: Stripe plugin, PayPal plugin, etc.
- Exports: Provider implementations, webhooks, API endpoints
Custom Plugins
- Purpose: Add business-specific functionality
- Examples: Usage tracking, custom billing rules, integrations
- Can combine: Schema, providers, and endpoints as needed
Development Guides
Schema Development
Learn how to define and extend database schemas using Zod objects.
Read Schema Guide →Basic Plugin Structure
Here's a simple plugin that adds a custom schema and endpoint:
import { createPlugin, createEndpoint } from "better-billing";
import { corePlugin } from "better-billing/plugins/core";
import { z } from "zod";
const analyticsPlugin = createPlugin(
(deps) => {
return {
// Extend database schema
schema: {
analytics: z.object({
id: z.string(),
event: z.string(),
userId: z.string(),
data: z.record(z.string()),
timestamp: z.date(),
}),
},
// Add API endpoints
endpoints: {
track: createEndpoint(
"/analytics/track",
{
method: "POST",
path: "/analytics/track",
},
async (request) => {
const { event, userId, data } = await request.json();
// Save analytics event
await deps.db.create("analytics", {
id: crypto.randomUUID(),
event,
userId,
data,
timestamp: new Date(),
});
return { success: true };
}
),
},
};
},
{
dependsOn: [corePlugin] as const,
}
);Plugin Dependencies
The dependency system ensures:
- Proper Initialization Order: Dependencies are resolved before dependent plugins
- Type Safety: TypeScript ensures required dependencies are available
- Schema Access: Dependent plugins can access and extend parent schemas
const myPlugin = createPlugin(
(deps) => {
// deps contains resolved dependencies with proper types
return { /* plugin implementation */ };
},
{
dependsOn: [corePlugin, stripePlugin] as const,
}
);Plugin Composition
When multiple plugins are used together, Better Billing automatically:
Schema Merging
- Additive: New fields are added to existing tables
- Override: Later plugins can override field types
- Validation: Runtime validation using merged schemas
Provider Merging
- Capability-Based: Same provider can have multiple capabilities
- Method Composition: Methods from different plugins are combined
- Type Safety: Full TypeScript support for merged providers
Endpoint Merging
- Route Combination: All endpoints are available under
/api/billing/ - Conflict Resolution: Later plugins override earlier ones for same routes
- Automatic Routing: No manual routing configuration needed
Development Workflow
- Plan Your Plugin: Decide what functionality to add
- Define Dependencies: What other plugins do you need?
- Design Schema: What data structures are needed?
- Implement Logic: Create providers, endpoints, or both
- Test Integration: Verify it works with other plugins
- Document Usage: Help others understand how to use it
Best Practices
- Single Responsibility: Each plugin should have a clear purpose
- Proper Dependencies: Declare all plugin dependencies explicitly
- Schema Versioning: Use optional fields for backward compatibility
- Error Handling: Handle errors gracefully in endpoints and providers
- Type Safety: Leverage TypeScript for better development experience
- Testing: Write tests for your plugin functionality
Common Patterns
Adding Custom Fields
// Extend existing tables with custom fields
schema: {
subscription: z.object({
customField: z.string().optional(),
metadata: z.record(z.string()),
}),
}Custom Provider Capability
// Add new capability to existing provider
providers: [{
providerId: "stripe",
capability: "custom-feature",
methods: {
customMethod: async (params) => { /* implementation */ },
},
}]Webhook Handler
// Handle external webhooks
endpoints: {
webhook: createEndpoint("/custom/webhook", {
method: "POST",
path: "/custom/webhook",
}, async (request) => {
// Process webhook payload
return { received: true };
}),
}Next Steps
Ready to build your first plugin? Choose your development path:
- Schema-First: Start with Schema Development
- Provider-First: Begin with Provider Development
- API-First: Jump to API Endpoints
- Learn by Example: Explore Plugin Examples