Skip to main content

Plugin Anatomy

Detailed breakdown of plugin structure.

Directory Structure

plugins/my_plugin/
├── __init__.py # Package initialization
├── plugin.py # Main plugin class
├── plugin.json # Manifest file
├── models.py # Database models
├── resolvers.py # GraphQL resolvers
├── routes.py # REST endpoints
├── services.py # Business logic
├── migrations/ # Database migrations
└── frontend/ # React components

Manifest (plugin.json)

{
"name": "my_plugin",
"version": "1.0.0",
"description": "Plugin description",
"author": "Your Name",
"entry_point": "plugin.py",
"frontend_entry": "frontend/index.ts",
"dependencies": ["other_plugin"],
"permissions": ["content.read", "content.write"],
"settings_schema": {
"api_key": { "type": "string", "required": true }
}
}

Main Plugin Class

from core.hooks import hookimpl
from core.plugins.base import PluginBase

class MyPlugin(PluginBase):
name = "my_plugin"
version = "1.0.0"

def on_activate(self):
"""Called when plugin is activated."""
pass

def on_deactivate(self):
"""Called when plugin is deactivated."""
pass

@hookimpl
def after_content_save(self, entry_id: str):
"""Hook implementation."""
pass

plugin = MyPlugin()

Database Models

from sqlalchemy import Column, String, Integer
from core.database import Base

class MyModel(Base):
__tablename__ = "my_plugin_items"

id = Column(String, primary_key=True)
name = Column(String, nullable=False)
value = Column(Integer, default=0)

GraphQL Resolvers

import strawberry
from typing import List

@strawberry.type
class MyItem:
id: str
name: str

@strawberry.type
class MyPluginQuery:
@strawberry.field
def my_items(self) -> List[MyItem]:
return get_items()

REST Routes

from fastapi import APIRouter

router = APIRouter(prefix="/my-plugin")

@router.get("/items")
def list_items():
return {"items": get_items()}

Frontend Components

// frontend/pages/MyPluginPage.tsx
export const MyPluginPage = () => {
return <div>My Plugin</div>;
};