BlogNetwork Pro

Plugin Development Guide

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.

Note: This guide assumes basic knowledge of TypeScript, React, and Next.js App Router. Study the bundled plugins in lib/plugins/ for real-world examples.

Plugin Architecture Overview

The plugin system uses a modular folder-based approach:

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
Important: Cron job handlers must be placed inside the api/cron/ folder, not in a separate cron/ folder at the plugin root.

Key Components

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".

Important:
  • Plugins must be registered in the plugins database 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.

Critical: Admin pages require static imports in the catch-all file. You must manually add your plugin's admin page import to app/admin/plugins/[id]/page.tsx for it to work.

When a request comes to /admin/plugins/my-plugin:

  1. Parses the path to get plugin slug (my-plugin)
  2. Verifies the plugin exists and is enabled
  3. Looks up the component from the static imports map (pluginAdminPages)
  4. 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.

Critical: API routes require static imports in the catch-all file. You must manually add your plugin's route imports to app/api/plugins/[...path]/route.ts for them to work.

When a request comes to /api/plugins/my-plugin/webhook:

  1. Parses the path to get plugin slug and subpath
  2. Verifies the plugin exists and is enabled
  3. Looks up the handler from the static imports map (pluginApiRoutes)
  4. 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:

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"
    }
  ]
}
Important: The handler path is relative to the plugin root and must be inside the 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 * * *"
    }
  ]
}
Note: Cron routes go through the plugin catch-all at /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

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

4. Performance

5. Domain Isolation

Testing Your Plugin

Manual Testing Checklist

Development Workflow

  1. Create plugin files
  2. Run npm run build to register plugin
  3. Start dev server: npm run dev
  4. Enable plugin in admin panel
  5. Configure plugin for a test domain
  6. 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 Process (for users)

  1. Extract plugin package
  2. Copy lib/plugins/your-plugin-slug/ to BlogNetwork Pro's lib/plugins/
  3. Copy scripts/register-your-plugin-slug.ts to BlogNetwork Pro's scripts/
  4. Run npx tsx scripts/register-your-plugin-slug.ts to register plugin in database
  5. Run npm run build to rebuild application
  6. Enable plugin in admin panel (/admin/plugins)
  7. Configure plugin settings

Bundled Plugins Reference

Study the bundled plugins for implementation examples:

Tip: The PostForMe plugin is the most comprehensive example, demonstrating admin pages, API routes, cron jobs, hooks, and multi-platform integration. Review its code for patterns and best practices.

Additional Resources

Next Steps

Now that you understand plugin development: