Plugin Development
Plugin Structure

Plugin Structure

Anatomy of a plugin folder and how files are organized.

Basic Structure

my-plugin/
├── plugin.json      # Plugin manifest (required)
├── index.js         # Main entry point (required)
├── README.md        # Documentation
├── assets/          # Static files
│   ├── css/
│   └── js/
└── lib/             # Additional modules

plugin.json

The manifest file defines your plugin's metadata and configuration:

{
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "Brief description of what the plugin does",
  "author": "Your Name",
  "authorUrl": "https://yourwebsite.com",
  "homepage": "https://github.com/yourname/my-plugin",
  "license": "MIT",
  
  "main": "index.js",
  
  "permissions": [
    "hooks",
    "content:read",
    "content:write",
    "media:read",
    "media:write",
    "storage",
    "rest",
    "widgets",
    "shortcodes",
    "admin"
  ],
  
  "settings": [
    {
      "key": "enabled",
      "type": "boolean",
      "label": "Enable Plugin",
      "default": true
    },
    {
      "key": "apiKey",
      "type": "text",
      "label": "API Key",
      "description": "Enter your API key"
    }
  ],
  
  "dependencies": {
    "other-plugin": "^1.0.0"
  }
}

Required Fields

FieldTypeDescription
namestringDisplay name
versionstringSemantic version (e.g., "1.2.3")
mainstringEntry point file

Optional Fields

FieldTypeDescription
descriptionstringShort description
authorstringAuthor name
authorUrlstringAuthor's website
homepagestringPlugin homepage/repo
licensestringLicense type
permissionsarrayRequired permissions
settingsarrayConfigurable settings
dependenciesobjectRequired plugins

Permissions

Declare what your plugin needs access to:

PermissionDescription
hooksUse hooks and filters
content:readRead posts, pages, categories
content:writeCreate, update, delete content
media:readAccess media files
media:writeUpload, delete media
storagePlugin storage (key-value)
restCreate REST API endpoints
widgetsRegister widgets
shortcodesRegister shortcodes
adminAdd admin pages/settings

Plugin Settings

Define settings that appear in the admin panel:

{
  "settings": [
    {
      "key": "title",
      "type": "text",
      "label": "Widget Title",
      "placeholder": "Enter title...",
      "default": "My Widget"
    },
    {
      "key": "count",
      "type": "number",
      "label": "Number of Items",
      "min": 1,
      "max": 50,
      "default": 10
    },
    {
      "key": "style",
      "type": "select",
      "label": "Display Style",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "list", "label": "List" }
      ],
      "default": "grid"
    },
    {
      "key": "showImages",
      "type": "boolean",
      "label": "Show Images",
      "default": true
    },
    {
      "key": "apiKey",
      "type": "text",
      "label": "API Key",
      "description": "Get your key from example.com",
      "secret": true
    }
  ]
}

Setting Types

TypeDescriptionExtra Options
textSingle line textplaceholder, maxLength, secret
textareaMulti-line textrows, maxLength
numberNumeric inputmin, max, step
booleanToggle switch-
selectDropdownoptions
multiselectMultiple selectionoptions
colorColor picker-
imageImage upload-

index.js Structure

module.exports = async function(sdk) {
  // Plugin initialization code
  
  // Register hooks, shortcodes, widgets, etc.
  sdk.hooks.addAction('init', () => {
    console.log('Plugin initialized');
  });
  
  // Return lifecycle methods
  return {
    // Called when plugin is activated
    activate: async () => {
      // Setup tasks
    },
    
    // Called when plugin is deactivated
    deactivate: async () => {
      // Cleanup tasks
    },
    
    // Called when plugin is uninstalled
    uninstall: async () => {
      // Remove all plugin data
      await sdk.storage.deleteAll('*');
    }
  };
};

Assets

Include CSS and JavaScript with your plugin:

my-plugin/
├── assets/
│   ├── css/
│   │   └── style.css
│   └── js/
│       └── script.js

Register assets in your plugin:

module.exports = async function(sdk) {
  // Add styles to frontend
  sdk.hooks.addAction('theme_head', () => {
    return `<link rel="stylesheet" href="/plugins/my-plugin/assets/css/style.css">`;
  });
  
  // Add scripts to frontend
  sdk.hooks.addAction('theme_footer', () => {
    return `<script src="/plugins/my-plugin/assets/js/script.js"></script>`;
  });
  
  // Add to admin only
  sdk.hooks.addAction('admin_head', () => {
    return `<link rel="stylesheet" href="/plugins/my-plugin/assets/css/admin.css">`;
  });
  
  return {};
};

Organizing Larger Plugins

For complex plugins, split code into modules:

my-plugin/
├── plugin.json
├── index.js
├── lib/
│   ├── hooks.js
│   ├── shortcodes.js
│   ├── widgets.js
│   ├── admin.js
│   └── api.js
└── assets/
// index.js
const registerHooks = require('./lib/hooks');
const registerShortcodes = require('./lib/shortcodes');
const registerWidgets = require('./lib/widgets');
const registerAdmin = require('./lib/admin');
const registerAPI = require('./lib/api');
 
module.exports = async function(sdk) {
  registerHooks(sdk);
  registerShortcodes(sdk);
  registerWidgets(sdk);
  registerAdmin(sdk);
  registerAPI(sdk);
  
  return {
    activate: async () => { /* ... */ },
    deactivate: async () => { /* ... */ }
  };
};
// lib/shortcodes.js
module.exports = function(sdk) {
  sdk.shortcodes.register('my-shortcode', (attrs) => {
    return `<div>Shortcode output</div>`;
  });
};

Complete Example

// plugin.json
{
  "name": "Social Share",
  "version": "1.0.0",
  "description": "Add social sharing buttons to posts",
  "author": "Your Name",
  "main": "index.js",
  "permissions": ["hooks", "shortcodes", "storage"],
  "settings": [
    {
      "key": "platforms",
      "type": "multiselect",
      "label": "Platforms",
      "options": [
        { "value": "facebook", "label": "Facebook" },
        { "value": "twitter", "label": "Twitter" },
        { "value": "pinterest", "label": "Pinterest" },
        { "value": "linkedin", "label": "LinkedIn" }
      ],
      "default": ["facebook", "twitter", "pinterest"]
    },
    {
      "key": "position",
      "type": "select",
      "label": "Button Position",
      "options": [
        { "value": "before", "label": "Before Content" },
        { "value": "after", "label": "After Content" },
        { "value": "both", "label": "Both" }
      ],
      "default": "after"
    }
  ]
}
// index.js
module.exports = async function(sdk) {
  const settings = await sdk.storage.get('settings') || {
    platforms: ['facebook', 'twitter', 'pinterest'],
    position: 'after'
  };
  
  function renderButtons(url, title) {
    const encoded = {
      url: encodeURIComponent(url),
      title: encodeURIComponent(title)
    };
    
    const buttons = {
      facebook: `<a href="https://facebook.com/sharer/sharer.php?u=${encoded.url}">Facebook</a>`,
      twitter: `<a href="https://twitter.com/intent/tweet?url=${encoded.url}&text=${encoded.title}">Twitter</a>`,
      pinterest: `<a href="https://pinterest.com/pin/create/button/?url=${encoded.url}">Pinterest</a>`,
      linkedin: `<a href="https://linkedin.com/shareArticle?url=${encoded.url}">LinkedIn</a>`
    };
    
    let html = '<div class="social-share">';
    settings.platforms.forEach(p => {
      if (buttons[p]) html += buttons[p];
    });
    html += '</div>';
    
    return html;
  }
  
  // Add to post content
  sdk.hooks.addFilter('post.content', (content, post) => {
    if (!post || !post.url) return content;
    
    const buttons = renderButtons(post.url, post.title);
    
    if (settings.position === 'before') return buttons + content;
    if (settings.position === 'after') return content + buttons;
    return buttons + content + buttons;
  });
  
  // Also as shortcode
  sdk.shortcodes.register('share', (attrs) => {
    return renderButtons(attrs.url || '', attrs.title || '');
  });
  
  return {
    activate: async () => {},
    deactivate: async () => {}
  };
};