Plugin Development Guide
This guide explains how to create plugins for BlogNetwork Pro. Plugins allow you to extend functionality by adding new AI providers, marketing integrations, analytics tools, social media integrations, advertising networks, and custom features.
lib/plugins/ for real-world examples.
Plugin Architecture Overview
The plugin system uses a modular folder-based approach:
- Plugin Code - Each plugin has its own folder in
lib/plugins/[plugin-name]/ - Plugin Manifest -
plugin.jsonfile defines metadata, routes, and cron jobs - Plugin Metadata - Registered in the database (
pluginstable) - Plugin Configuration - Stored per-domain in
plugin_configstable - Auto-Discovery - Plugins are automatically discovered at build time
Directory Structure
lib/plugins/
├── core/ # Plugin system core (DO NOT MODIFY)
│ ├── plugin-manager.ts # Plugin lifecycle & loading
│ ├── plugin-registry.ts # Runtime registry
│ ├── plugin-hooks.ts # Event system
│ ├── plugin-types.ts # TypeScript interfaces
│ ├── plugin-route-types.ts # Route handler types
│ └── generated-registry.ts # Auto-generated plugin map
├── base/ # Base provider classes
│ ├── base-ai-provider.ts
│ ├── base-marketing-provider.ts
│ ├── base-analytics-provider.tsx
│ ├── base-social-provider.ts
│ └── base-advertising-provider.tsx
└── [plugin-name]/ # Your plugin folder
├── plugin.json # REQUIRED: Plugin manifest
├── provider.ts # REQUIRED: Main plugin class
├── types.ts # Optional: Plugin-specific types
├── admin/ # Optional: Admin UI pages
│ ├── page.tsx # Main admin page
│ └── [subpage]/page.tsx # Nested pages
├── api/ # Optional: API routes
│ ├── route.ts # Base API handler
│ ├── [id]/route.ts # Item API handler
│ └── cron/ # Cron handlers (INSIDE api/ folder)
│ └── [job-name]/route.ts
└── components/ # Optional: React components
api/cron/ folder, not in a separate cron/ folder at the plugin root.
Key Components
- Plugin Manager (
lib/plugins/core/plugin-manager.ts) - Manages plugin lifecycle, loading, and configuration - Plugin Registry (
lib/plugins/core/plugin-registry.ts) - Runtime registry of loaded plugins - Plugin Hooks (
lib/plugins/core/plugin-hooks.ts) - Event system for plugin interactions - Plugin Types (
lib/plugins/core/plugin-types.ts) - TypeScript interfaces and types - Generated Registry (
lib/plugins/core/generated-registry.ts) - Auto-generated at build time
The plugin.json Manifest
Every plugin requires a plugin.json manifest file in its root folder. This file defines the plugin's metadata, routes, and scheduled jobs.
Complete Manifest Structure
{
"id": "my-plugin", // Unique identifier (required)
"name": "My Plugin Display Name", // Human-readable name (required)
"slug": "my-plugin", // URL-safe slug (required, must match folder name)
"type": "social", // Plugin type (required)
"version": "1.0.0", // Semantic version (required)
"description": "Plugin description", // Optional
"author": "Your Name", // Optional
"bundled": false, // true = ships with core, false = add-on (required)
"entryPoint": "./provider.ts", // Path to main provider file (required)
"routes": { // Optional: Define admin and API routes
"admin": {
"base": "/admin/plugins/my-plugin",
"pages": ["", "settings", "accounts"]
},
"api": {
"base": "/api/plugins/my-plugin",
"routes": ["", "webhook", "accounts/[id]"]
}
},
"adminNav": { // Optional: Add item to admin navigation
"label": "My Plugin", // Display text in nav bar
"href": "/admin/plugins/my-plugin", // Link destination
"mainDomainOnly": false // true = only show on main domain (default: true)
},
"cron": [ // Optional: Scheduled jobs
{
"name": "process-queue",
"schedule": "*/5 * * * *",
"description": "Process queue every 5 minutes",
"handler": "api/cron/process-queue/route.ts" // Path relative to plugin root, INSIDE api/
}
],
"dependencies": [], // Optional: Other plugin slugs required
"minCoreVersion": "1.0.0" // Optional: Minimum BlogNetwork Pro version
}
Example: Simple Plugin Manifest
{
"id": "adsense",
"name": "Google AdSense",
"slug": "adsense",
"type": "advertising",
"version": "1.0.0",
"description": "Google AdSense integration for displaying ads",
"author": "BlogNetwork Pro",
"bundled": false,
"entryPoint": "./provider.tsx",
"routes": {
"api": {
"base": "/api/plugins/adsense",
"routes": ["ads"]
}
}
}
Example: Full-Featured Plugin Manifest
{
"id": "postforme",
"name": "Social Media Automation",
"slug": "postforme",
"type": "social",
"version": "1.0.0",
"description": "Auto-post to Instagram, Facebook, X, LinkedIn, and more",
"author": "BlogNetwork Pro",
"bundled": false,
"entryPoint": "./provider.ts",
"routes": {
"admin": {
"base": "/admin/plugins/postforme",
"pages": ["", "accounts", "queue", "templates", "settings"]
},
"api": {
"base": "/api/plugins/postforme",
"routes": [
"",
"accounts",
"accounts/[id]",
"accounts/[id]/toggle",
"connect/[platform]",
"connect/[platform]/callback",
"posts",
"posts/[id]",
"webhook"
]
}
},
"cron": [
{
"name": "process-queue",
"schedule": "*/5 * * * *",
"description": "Process scheduled social posts",
"handler": "api/cron/process-queue/route.ts"
},
{
"name": "refresh-tokens",
"schedule": "0 * * * *",
"description": "Refresh expiring OAuth tokens",
"handler": "api/cron/refresh-tokens/route.ts"
}
]
}
Admin Navigation
Plugins can add items to the admin navigation bar by including an adminNav field in plugin.json.
adminNav Structure
{
"adminNav": {
"label": "Content Plans",
"href": "/admin/plugins/content-planner",
"mainDomainOnly": false
}
}
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | Yes | Text displayed in the navigation bar |
href |
string | Yes | Link destination (typically /admin/plugins/{slug}) |
mainDomainOnly |
boolean | No | If true, only shows on main domain. Default: true |
When a plugin with adminNav is enabled, the nav item automatically appears in the admin header. When the plugin is disabled, the nav item is removed.
Example
From the Content Planner plugin (lib/plugins/content-planner/plugin.json):
{
"id": "content-planner",
"name": "Content Planner",
"slug": "content-planner",
"adminNav": {
"label": "Content Plans",
"href": "/admin/plugins/content-planner",
"mainDomainOnly": false
}
}
This adds "Content Plans" to the admin navigation bar for all domains (not just main domain).
Plugin Types
BlogNetwork Pro supports six types of plugins:
1. AI Provider (ai)
Handles content generation tasks:
- Blog post generation
- FAQ generation
- CTA generation
- Organization analysis
Base Class: BaseAIProvider from lib/plugins/base/base-ai-provider.ts
Interface: AIProvider
2. Marketing Provider (marketing)
Handles contact/lead management:
- Email capture form submissions
- Contact management
- Lead tracking
Base Class: BaseMarketingProvider from lib/plugins/base/base-marketing-provider.ts
Interface: MarketingProvider
3. Analytics Provider (analytics)
Handles website analytics:
- Analytics script injection
- Tracking code rendering
Base Class: BaseAnalyticsProvider from lib/plugins/base/base-analytics-provider.tsx
Interface: AnalyticsProvider
4. Social Provider (social)
Handles social media integrations:
- Auto-posting to social networks
- Social media account management
- Multi-platform posting
Base Class: BaseSocialProvider from lib/plugins/base/base-social-provider.ts
Interfaces: SocialProvider (single platform) or UnifiedSocialProvider (multi-platform)
5. Advertising Provider (advertising)
Handles ad display and management:
- Ad display and management
- Ad slot configuration
- Context-aware ad rendering
Base Class: BaseAdvertisingProvider from lib/plugins/base/base-advertising-provider.tsx
Interface: AdvertisingProvider
6. Custom (custom)
Any custom functionality that doesn't fit other types
Base Interface: Plugin
Creating a Plugin
Step 1: Create Plugin Folder
Create a new folder in lib/plugins/ with your plugin's slug name:
lib/plugins/my-plugin/
Step 2: Create plugin.json Manifest
Create a plugin.json file in your plugin folder:
{
"id": "my-plugin",
"name": "My Custom Plugin",
"slug": "my-plugin",
"type": "custom",
"version": "1.0.0",
"description": "Description of what this plugin does",
"author": "Your Name",
"bundled": false,
"entryPoint": "./provider.ts"
}
Step 3: Create Provider Class
Create your main provider file (e.g., provider.ts):
import type {
Plugin,
PluginMetadata,
PluginConfig,
ConfigSchema,
} from '../core/plugin-types';
class MyPlugin implements Plugin {
metadata: PluginMetadata = {
id: 'my-plugin',
name: 'My Custom Plugin',
slug: 'my-plugin',
type: 'custom',
version: '1.0.0',
description: 'Description of what this plugin does',
author: 'Your Name',
bundled: false,
enabled: true,
};
private config: PluginConfig = {};
async initialize(config: PluginConfig): Promise<void> {
this.config = config;
// Validate required config
if (!config.apiKey) {
throw new Error('API key is required');
}
}
getConfigSchema(): ConfigSchema {
return {
fields: [
{
name: 'apiKey',
label: 'API Key',
type: 'password',
description: 'Your API key',
required: true,
},
],
};
}
}
export default new MyPlugin();
Step 4: Create Registration Script
Create a script to register your plugin in the database. Save as scripts/register-my-plugin.ts:
import { sql } from '../lib/db/sql';
async function registerPlugin() {
console.log('Registering my-plugin...');
const existing = await sql`SELECT id FROM plugins WHERE slug = 'my-plugin' LIMIT 1`;
if (existing.length > 0) {
console.log('Plugin already registered');
process.exit(0);
}
await sql`
INSERT INTO plugins (name, slug, type, version, description, author, bundled, enabled)
VALUES ('My Custom Plugin', 'my-plugin', 'custom', '1.0.0', 'Description', 'Author', 0, 0)
`;
console.log('Plugin registered! Enable it in /admin/plugins');
process.exit(0);
}
registerPlugin();
Step 5: Register Plugin in Database
Run your registration script to add the plugin to the database:
npx tsx scripts/register-my-plugin.ts
Step 6: Register API Routes (If Your Plugin Has API Routes)
This step is critical. Add static imports for your plugin's API routes to app/api/plugins/[...path]/route.ts:
// Add static imports at top of file
import * as MyPluginApi from '@/lib/plugins/my-plugin/api/route';
// Add to pluginApiRoutes map
const pluginApiRoutes: Record<string, Record<string, PluginRouteModule>> = {
// ... existing plugins ...
'my-plugin': {
'': MyPluginApi,
},
};
Step 7: Register Admin Pages (If Your Plugin Has Admin UI)
Add your admin page component to app/admin/plugins/[id]/page.tsx:
// Add static import at top of file
import MyPluginAdminPage from "@/lib/plugins/my-plugin/admin/page";
// Add to pluginAdminPages map
const pluginAdminPages: Record<string, React.ComponentType<PluginAdminPageProps>> = {
// ... existing plugins ...
"my-plugin": MyPluginAdminPage,
};
Step 8: Rebuild Application
The build process discovers plugin provider files and generates the import map:
npm run build
This runs scripts/generate-plugin-registry.ts which scans for plugin.json files and generates provider imports.
Step 9: Enable in Admin Panel
Go to /admin/plugins in your admin panel. Find your plugin and click "Enable".
- Plugins must be registered in the
pluginsdatabase table (Step 5) - API routes must be added to the static import map (Step 6)
- Admin pages must be added to the static import map (Step 7)
Plugin Interfaces
Base Plugin Interface
All plugins must implement the base Plugin interface:
interface Plugin {
metadata: PluginMetadata;
initialize(config: PluginConfig): Promise<void>;
getConfigSchema(): ConfigSchema;
}
PluginMetadata
interface PluginMetadata {
id: string;
name: string;
slug: string;
type: PluginType; // 'ai' | 'marketing' | 'analytics' | 'social' | 'advertising' | 'custom'
version: string;
description?: string;
author?: string;
bundled: boolean;
codecanyonUrl?: string; // For add-on plugins with marketplace listing
enabled: boolean;
}
AI Provider Interface
interface AIProvider extends Plugin {
generateBlogPost(input: BlogGenerationInput): Promise<GeneratedBlogPost>;
generateFAQs(content: string): Promise<{ faqs: FAQ[]; tokenUsage?: TokenUsage }>;
generateCTAs(input: CTAGenerationInput): Promise<CTAResult[]>;
analyzeOrganization(url: string): Promise<OrganizationAnalysisResult>;
}
Marketing Provider Interface
interface MarketingProvider extends Plugin {
submitContact(data: ContactData): Promise<ContactResult>;
}
Analytics Provider Interface
interface AnalyticsProvider extends Plugin {
renderScript(measurementId: string): ReactNode;
}
Social Provider Interface (Single Platform)
interface SocialProvider extends Plugin {
postToSocial(data: SocialPostData): Promise<SocialPostResult>;
}
Unified Social Provider Interface (Multi-Platform)
interface UnifiedSocialProvider extends Plugin {
postToMultiplePlatforms(data: UnifiedSocialPostData): Promise<UnifiedSocialPostResult>;
}
interface UnifiedSocialPostData {
postId: number;
title: string;
content: string;
excerpt?: string;
url: string;
publishDate: Date;
domain: string;
platforms: string[]; // Array of platform identifiers
}
interface UnifiedSocialPostResult {
success: boolean;
results: {
platform: string;
success: boolean;
postId?: string;
message?: string;
error?: string;
}[];
}
Advertising Provider Interface
interface AdvertisingProvider extends Plugin {
renderAd(adSlot: string, context: AdContext): ReactNode;
getAdSlots(): string[];
}
interface AdContext {
domainId: number;
categoryId?: number;
tagId?: number;
pageType: 'home' | 'blog' | 'category' | 'tag';
}
Admin Pages
Plugins can provide custom admin UI pages that integrate seamlessly with the admin dashboard.
How Admin Routing Works
A catch-all route at app/admin/plugins/[id]/page.tsx handles plugin admin routes.
app/admin/plugins/[id]/page.tsx for it to work.
When a request comes to /admin/plugins/my-plugin:
- Parses the path to get plugin slug (
my-plugin) - Verifies the plugin exists and is enabled
- Looks up the component from the static imports map (
pluginAdminPages) - Renders the component with plugin context
Registering Admin Pages (REQUIRED)
Add your admin page to app/admin/plugins/[id]/page.tsx:
// Add static import at top of file
import MyPluginAdminPage from "@/lib/plugins/my-plugin/admin/page";
// Add to pluginAdminPages map
const pluginAdminPages: Record<string, React.ComponentType<PluginAdminPageProps>> = {
"content-planner": ContentPlannerAdminPage,
"my-plugin": MyPluginAdminPage, // ADD THIS
};
Admin Page Structure
lib/plugins/my-plugin/
├── admin/
│ ├── page.tsx # /admin/plugins/my-plugin
│ ├── settings/
│ │ └── page.tsx # /admin/plugins/my-plugin/settings
│ ├── accounts/
│ │ └── page.tsx # /admin/plugins/my-plugin/accounts
│ └── [id]/
│ └── page.tsx # /admin/plugins/my-plugin/:id
Admin Page Props
interface PluginAdminPageProps {
params: Record<string, string>; // Dynamic route params like { id: '123' }
pluginSlug: string; // Your plugin's slug
searchParams: Record<string, string | string[] | undefined>;
}
Example Admin Page
// lib/plugins/my-plugin/admin/page.tsx
import { sql } from '@/lib/db/sql';
import { requireAuth } from '@/lib/auth/auth';
import { getAdminDomainContext } from '@/lib/admin/domain-context';
import Link from 'next/link';
export default async function MyPluginDashboard() {
await requireAuth();
const domainContext = await getAdminDomainContext();
// Fetch plugin-specific data
const stats = await sql`
SELECT COUNT(*) as count FROM my_plugin_table
WHERE domain_id = ${domainContext.domainId}
`;
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">
My Plugin Dashboard
</h1>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow">
<h3 className="text-sm text-gray-500">Total Items</h3>
<p className="text-2xl font-bold">{stats[0]?.count || 0}</p>
</div>
</div>
{/* Navigation */}
<div className="flex gap-4">
<Link
href="/admin/plugins/my-plugin/settings"
className="px-4 py-2 bg-blue-600 text-white rounded-md"
>
Settings
</Link>
</div>
</div>
);
}
API Routes
Plugins can provide custom API endpoints that integrate with the application's API infrastructure.
How API Routing Works
A catch-all route at app/api/plugins/[...path]/route.ts handles all plugin API routes.
app/api/plugins/[...path]/route.ts for them to work.
When a request comes to /api/plugins/my-plugin/webhook:
- Parses the path to get plugin slug and subpath
- Verifies the plugin exists and is enabled
- Looks up the handler from the static imports map (
pluginApiRoutes) - Calls the appropriate HTTP method handler (GET, POST, etc.)
Registering API Routes (REQUIRED)
Add your API routes to app/api/plugins/[...path]/route.ts:
// Add static imports at top of file
import * as MyPluginApi from '@/lib/plugins/my-plugin/api/route';
import * as MyPluginIdApi from '@/lib/plugins/my-plugin/api/[id]/route';
import * as MyPluginWebhookApi from '@/lib/plugins/my-plugin/api/webhook/route';
// Add to pluginApiRoutes map
const pluginApiRoutes: Record<string, Record<string, PluginRouteModule>> = {
'content-planner': {
'': ContentPlannerApi,
'[id]': ContentPlannerIdApi,
'[id]/generate': ContentPlannerGenerateApi,
},
// ADD YOUR PLUGIN:
'my-plugin': {
'': MyPluginApi, // /api/plugins/my-plugin
'[id]': MyPluginIdApi, // /api/plugins/my-plugin/123
'webhook': MyPluginWebhookApi, // /api/plugins/my-plugin/webhook
},
};
The path key uses normalized patterns where numeric IDs become [id]. For example:
- URL
/api/plugins/my-plugin→ path key'' - URL
/api/plugins/my-plugin/123→ path key'[id]' - URL
/api/plugins/my-plugin/123/action→ path key'[id]/action'
API Route Structure
lib/plugins/my-plugin/
├── api/
│ ├── route.ts # /api/plugins/my-plugin
│ ├── webhook/
│ │ └── route.ts # /api/plugins/my-plugin/webhook
│ └── items/
│ ├── route.ts # /api/plugins/my-plugin/items
│ └── [id]/
│ └── route.ts # /api/plugins/my-plugin/items/:id
Route Handler Context
interface PluginRouteContext {
params: Record<string, string>; // Dynamic route params
pluginSlug: string;
subPath: string[]; // Remaining path segments
domainContext: {
isMainDomain: boolean;
domainId: number | null;
domainName: string | null;
};
}
Example API Route Handler
// lib/plugins/my-plugin/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { sql } from '@/lib/db/sql';
import type { PluginRouteContext } from '@/lib/plugins/core/plugin-route-types';
export async function GET(
request: NextRequest,
context: PluginRouteContext
): Promise<NextResponse> {
const { domainContext } = context;
const items = await sql`
SELECT * FROM my_plugin_items
WHERE domain_id = ${domainContext.domainId}
ORDER BY created_at DESC
`;
return NextResponse.json({ items });
}
export async function POST(
request: NextRequest,
context: PluginRouteContext
): Promise<NextResponse> {
const body = await request.json();
const { domainContext } = context;
const result = await sql`
INSERT INTO my_plugin_items (domain_id, name, data)
VALUES (${domainContext.domainId}, ${body.name}, ${JSON.stringify(body.data)})
RETURNING *
`;
return NextResponse.json({ item: result[0] }, { status: 201 });
}
Example with Dynamic Parameters
// lib/plugins/my-plugin/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { sql } from '@/lib/db/sql';
import type { PluginRouteContext } from '@/lib/plugins/core/plugin-route-types';
export async function GET(
request: NextRequest,
context: PluginRouteContext
): Promise<NextResponse> {
const { id } = context.params; // From URL path
const result = await sql`
SELECT * FROM my_plugin_items WHERE id = ${id}
`;
if (result.length === 0) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json({ item: result[0] });
}
export async function DELETE(
request: NextRequest,
context: PluginRouteContext
): Promise<NextResponse> {
const { id } = context.params;
await sql`DELETE FROM my_plugin_items WHERE id = ${id}`;
return NextResponse.json({ success: true });
}
Cron Jobs
Plugins can define scheduled jobs that run automatically on a cron schedule.
Defining Cron Jobs in plugin.json
{
"cron": [
{
"name": "process-queue",
"schedule": "*/5 * * * *",
"description": "Process queue every 5 minutes",
"handler": "api/cron/process-queue/route.ts"
},
{
"name": "daily-cleanup",
"schedule": "0 0 * * *",
"description": "Clean up old data daily at midnight",
"handler": "api/cron/cleanup/route.ts"
}
]
}
api/cron/ folder.
Cron Handler Structure
lib/plugins/my-plugin/
├── api/
│ ├── route.ts # Regular API routes
│ └── cron/ # Cron handlers INSIDE api/
│ ├── process-queue/
│ │ └── route.ts
│ └── cleanup/
│ └── route.ts
Example Cron Handler
// lib/plugins/my-plugin/api/cron/process-queue/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { sql } from '@/lib/db/sql';
export async function GET(request: NextRequest): Promise<NextResponse> {
// Verify cron secret (for Vercel cron)
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
// Get pending items
const pendingItems = await sql`
SELECT * FROM my_plugin_queue
WHERE status = 'pending'
LIMIT 10
`;
// Process each item
for (const item of pendingItems) {
await processItem(item);
await sql`
UPDATE my_plugin_queue
SET status = 'completed', processed_at = NOW()
WHERE id = ${item.id}
`;
}
return NextResponse.json({
success: true,
processed: pendingItems.length,
});
} catch (error) {
console.error('Cron job failed:', error);
return NextResponse.json({ error: 'Processing failed' }, { status: 500 });
}
}
Vercel Cron Configuration
For Vercel deployments, add your cron jobs to vercel.json:
{
"crons": [
{
"path": "/api/plugins/my-plugin/cron/process-queue",
"schedule": "*/5 * * * *"
},
{
"path": "/api/plugins/my-plugin/cron/cleanup",
"schedule": "0 0 * * *"
}
]
}
/api/plugins/[slug]/cron/[job]. The catch-all route automatically skips auth for paths containing 'cron' (they use CRON_SECRET instead).
Configuration Schemas
Plugins define their configuration requirements using a ConfigSchema. This schema is used to render the configuration form in the admin UI.
interface ConfigSchema {
fields: ConfigField[];
}
interface ConfigField {
name: string;
label: string;
type: 'text' | 'password' | 'textarea' | 'number' | 'boolean' | 'select';
description?: string;
placeholder?: string;
required?: boolean;
defaultValue?: string | number | boolean | null;
options?: Array<{ label: string; value: string }>; // For select fields
helpModal?: string; // Identifier for help modal (e.g., 'gohighlevel-setup')
}
Example Config Schema
getConfigSchema(): ConfigSchema {
return {
fields: [
{
name: 'apiKey',
label: 'API Key',
type: 'password',
description: 'Your API key for authentication',
required: true,
},
{
name: 'locationId',
label: 'Location ID',
type: 'text',
description: 'Your location identifier',
placeholder: 'Enter location ID',
required: true,
},
{
name: 'enableTracking',
label: 'Enable Tracking',
type: 'boolean',
description: 'Enable advanced tracking features',
defaultValue: false,
},
{
name: 'postFrequency',
label: 'Post Frequency',
type: 'select',
description: 'How often to auto-post',
options: [
{ label: 'Every post', value: 'every' },
{ label: 'Daily digest', value: 'daily' },
{ label: 'Weekly digest', value: 'weekly' },
],
defaultValue: 'every',
},
],
};
}
Plugin Hooks
Plugins can register hooks to respond to application events. This enables plugins to react to post publishing, contact submissions, and other actions.
Available Hooks
// From lib/plugins/core/plugin-hooks.ts
export const HookNames = {
POST_CREATED: 'post.created',
POST_PUBLISHED: 'post.published',
POST_UPDATED: 'post.updated',
POST_DELETED: 'post.deleted',
CONTACT_SUBMITTED: 'contact.submitted',
DOMAIN_CREATED: 'domain.created',
DOMAIN_UPDATED: 'domain.updated',
} as const;
Registering Hooks
import { pluginHooks, HookNames } from '@/lib/plugins/core/plugin-hooks';
import type { HookHandler } from '@/lib/plugins/core/plugin-types';
// In your plugin's initialize method
async initialize(config: PluginConfig): Promise<void> {
this.config = config;
// Register a hook to respond to published posts
const handler: HookHandler = async (data: unknown) => {
const postData = data as { postId: number; title: string; url: string };
await this.handlePostPublished(postData);
};
pluginHooks.register({
pluginId: this.metadata.slug,
hookName: HookNames.POST_PUBLISHED,
handler: handler,
priority: 10, // lower number = higher priority
});
}
private async handlePostPublished(data: { postId: number; title: string; url: string }) {
// Auto-post to social media, send notifications, etc.
console.log(`Post published: ${data.title}`);
}
Hook Data Types
POST_CREATED/POST_PUBLISHED/POST_UPDATED:{ postId: number; title: string; url: string; domain: string }POST_DELETED:{ postId: number }CONTACT_SUBMITTED:{ email: string; fullName?: string; domain: string }DOMAIN_CREATED/DOMAIN_UPDATED:{ domainId: number; domainName: string }
Example: Creating an AI Provider Plugin
Here's a complete example of creating an OpenAI provider plugin:
plugin.json
{
"id": "openai",
"name": "OpenAI GPT-4",
"slug": "openai",
"type": "ai",
"version": "1.0.0",
"description": "OpenAI GPT-4 for content generation",
"author": "Your Name",
"bundled": false,
"entryPoint": "./provider.ts"
}
provider.ts
import type {
AIProvider,
PluginMetadata,
PluginConfig,
ConfigSchema,
BlogGenerationInput,
GeneratedBlogPost,
FAQ,
TokenUsage,
CTAGenerationInput,
CTAResult,
OrganizationAnalysisResult,
} from '../core/plugin-types';
class OpenAIProvider implements AIProvider {
metadata: PluginMetadata = {
id: 'openai',
name: 'OpenAI GPT-4',
slug: 'openai',
type: 'ai',
version: '1.0.0',
description: 'OpenAI GPT-4 for content generation',
author: 'Your Name',
bundled: false,
enabled: true,
};
private apiKey: string = '';
private model: string = 'gpt-4';
async initialize(config: PluginConfig): Promise<void> {
this.apiKey = config.apiKey as string;
this.model = (config.model as string) || 'gpt-4';
if (!this.apiKey) {
throw new Error('OpenAI API key is required');
}
}
getConfigSchema(): ConfigSchema {
return {
fields: [
{
name: 'apiKey',
label: 'OpenAI API Key',
type: 'password',
description: 'Your OpenAI API key from platform.openai.com',
required: true,
},
{
name: 'model',
label: 'Model',
type: 'select',
description: 'GPT model to use',
options: [
{ label: 'GPT-4', value: 'gpt-4' },
{ label: 'GPT-4 Turbo', value: 'gpt-4-turbo' },
{ label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
],
defaultValue: 'gpt-4',
},
],
};
}
async generateBlogPost(input: BlogGenerationInput): Promise<GeneratedBlogPost> {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
model: this.model,
messages: [
{
role: 'system',
content: 'You are a professional blog writer.',
},
{
role: 'user',
content: `Write a blog post about: ${input.topic || input.title}`,
},
],
}),
});
const data = await response.json();
// Parse and return GeneratedBlogPost structure
// ...
}
async generateFAQs(content: string): Promise<{ faqs: FAQ[]; tokenUsage?: TokenUsage }> {
// Implementation
}
async generateCTAs(input: CTAGenerationInput): Promise<CTAResult[]> {
// Implementation
}
async analyzeOrganization(url: string): Promise<OrganizationAnalysisResult> {
// Implementation
}
}
export default new OpenAIProvider();
Best Practices
1. Error Handling
Always handle errors gracefully:
async generateBlogPost(input: BlogGenerationInput): Promise<GeneratedBlogPost> {
try {
// Your implementation
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Error generating blog post:', error);
throw new Error(`Failed to generate blog post: ${message}`);
}
}
2. Configuration Validation
Validate configuration in initialize():
async initialize(config: PluginConfig): Promise<void> {
if (!config.apiKey) {
throw new Error('API key is required');
}
if (typeof config.apiKey !== 'string') {
throw new Error('API key must be a string');
}
if (config.apiKey.length < 10) {
throw new Error('API key appears to be invalid');
}
this.apiKey = config.apiKey;
}
3. Security
- Never log sensitive data (API keys, passwords, tokens)
- Use
passwordtype for sensitive fields in config schema - Validate all user inputs before processing
- Sanitize data before sending to external APIs
- Verify cron secrets for scheduled jobs
4. Performance
- Cache API responses when appropriate
- Use efficient database queries with proper indexes
- Avoid blocking operations in hook handlers
- Implement rate limiting for external API calls
- Use streaming for large content generation
5. Domain Isolation
- Always filter database queries by
domain_id - Respect the
domainContextin API routes - Store domain-specific configuration in
plugin_configs
Testing Your Plugin
Manual Testing Checklist
- Plugin loads without errors (check console)
- Plugin appears in admin Plugins page
- Configuration form displays all fields correctly
- Configuration saves and loads properly
- Plugin functions work as expected
- Errors are handled gracefully with user-friendly messages
- Admin pages render correctly
- API routes respond with correct status codes
- Hooks fire when expected
- Plugin works with multiple domains
Development Workflow
- Create plugin files
- Run
npm run buildto register plugin - Start dev server:
npm run dev - Enable plugin in admin panel
- Configure plugin for a test domain
- Test all functionality through the UI and API
Distribution
For CodeCanyon or third-party distribution, use the included packaging script:
Package Your Plugin
./scripts/package-plugin.sh your-plugin-slug
This creates a distribution package at plugin-dist/your-plugin-slug-vX.X.X.zip
Package Contents
your-plugin-slug/
├── lib/
│ └── plugins/
│ └── your-plugin-slug/
│ ├── plugin.json
│ ├── provider.ts (or .tsx)
│ ├── types.ts
│ ├── admin/
│ ├── api/
│ └── components/
├── scripts/
│ └── register-your-plugin-slug.ts # Database registration script
├── README.md # Auto-generated
├── CHANGELOG.md # Auto-generated
└── INSTALL.md # Installation instructions
Documentation Requirements
- Installation instructions (INSTALL.md)
- Database registration script
- Configuration guide for each platform/service
- API requirements and how to get credentials
- Troubleshooting common issues
- Changelog with version history
Installation Process (for users)
- Extract plugin package
- Copy
lib/plugins/your-plugin-slug/to BlogNetwork Pro'slib/plugins/ - Copy
scripts/register-your-plugin-slug.tsto BlogNetwork Pro'sscripts/ - Run
npx tsx scripts/register-your-plugin-slug.tsto register plugin in database - Run
npm run buildto rebuild application - Enable plugin in admin panel (
/admin/plugins) - Configure plugin settings
Bundled Plugins Reference
Study the bundled plugins for implementation examples:
- Claude AI Provider -
lib/plugins/claude/provider.ts - GoHighLevel Marketing -
lib/plugins/gohighlevel/provider.ts - Google Analytics 4 -
lib/plugins/ga4/provider.tsx - Google AdSense -
lib/plugins/adsense/provider.tsx - Content Planner -
lib/plugins/content-planner/provider.ts - PostForMe Social -
lib/plugins/postforme/provider.ts(full-featured example with admin pages, API routes, and cron jobs)
Additional Resources
- Review TypeScript types in
lib/plugins/core/plugin-types.ts - Study route handling in
lib/plugins/core/plugin-route-types.ts - Examine the plugin manager in
lib/plugins/core/plugin-manager.ts - Check the auto-discovery script at
scripts/generate-plugin-registry.ts
Next Steps
Now that you understand plugin development:
- Review the PostForMe plugin as a comprehensive example
- Start with a simple plugin to learn the system
- Use the package script for distribution
- Test thoroughly before distributing
- Document your plugin well for users