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 modulesplugin.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
| Field | Type | Description |
|---|---|---|
name | string | Display name |
version | string | Semantic version (e.g., "1.2.3") |
main | string | Entry point file |
Optional Fields
| Field | Type | Description |
|---|---|---|
description | string | Short description |
author | string | Author name |
authorUrl | string | Author's website |
homepage | string | Plugin homepage/repo |
license | string | License type |
permissions | array | Required permissions |
settings | array | Configurable settings |
dependencies | object | Required plugins |
Permissions
Declare what your plugin needs access to:
| Permission | Description |
|---|---|
hooks | Use hooks and filters |
content:read | Read posts, pages, categories |
content:write | Create, update, delete content |
media:read | Access media files |
media:write | Upload, delete media |
storage | Plugin storage (key-value) |
rest | Create REST API endpoints |
widgets | Register widgets |
shortcodes | Register shortcodes |
admin | Add 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
| Type | Description | Extra Options |
|---|---|---|
text | Single line text | placeholder, maxLength, secret |
textarea | Multi-line text | rows, maxLength |
number | Numeric input | min, max, step |
boolean | Toggle switch | - |
select | Dropdown | options |
multiselect | Multiple selection | options |
color | Color picker | - |
image | Image 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.jsRegister 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 () => {}
};
};