Skip to content
Asterios Raptis edited this page May 21, 2026 · 4 revisions

Examples

Minimal Plugin

frompluginforgeimportBasePluginclassMinimalPlugin(BasePlugin):
name="minimal"

This is a valid plugin. All lifecycle methods have default implementations. Since v0.9.0, plugins without target_application are filtered when the host has adopted app_id; see the next example for the recommended shape.

Minimal Plugin (v0.7.0 recommended)

frompluginforgeimportBasePluginclassMinimalPlugin(BasePlugin):
name="minimal"target_application="myapp"# v0.7.0: declare which host you target

When the host runs with PluginManager(app_id="myapp"), this plugin activates without identity-related warnings. When the host has not adopted app_id, the plugin still activates (claim unvalidated).

Plugin with Config

frompluginforgeimportBasePluginclassNotifierPlugin(BasePlugin):
name="notifier"version="1.0.0"description="Send notifications"defactivate(self) ->None:
self.channel=self.config.get("channel", "email")
self.recipients=self.config.get("recipients", [])
# config/plugins/notifier.yamlchannel: "slack"recipients:
- "#general"
- "#alerts"

Plugin with Dependencies

classAnalyticsPlugin(BasePlugin):
name="analytics"depends_on= ["storage"]
defactivate(self) ->None:
# "storage" is guaranteed to be active at this pointpass

Plugin with Hooks

importpluggyfrompluginforgeimportBasePluginhookimpl=pluggy.HookimplMarker("myapp")
classAuditPlugin(BasePlugin):
name="audit"defactivate(self) ->None:
self.log= []
@hookimpldefon_document_save(self, document: dict) ->None:
self.log.append({"action": "save", "doc": document["title"]})
@hookimpldefon_document_delete(self, document_id: str) ->None:
self.log.append({"action": "delete", "id": document_id})

Plugin with FastAPI Routes

fromfastapiimportAPIRouterfrompluginforgeimportBasePluginclassHealthPlugin(BasePlugin):
name="health"defget_routes(self) ->list:
router=APIRouter()
@router.get("/status")defstatus():
return {"status": "ok", "plugin_version": self.version}
return [router]

Accessible at: GET /api/status (mount_routes prepends /api; the router has no internal prefix, so the route lands at /api/status rather than under a per-plugin namespace. To namespace by plugin, give the router an explicit prefix="/health" — that yields /api/health/status.)

Full Application

# app.pyfromcontextlibimportasynccontextmanagerimportpluggyfromfastapiimportFastAPIfrompluginforgeimportPluginManager# Define hook specshookspec=pluggy.HookspecMarker("myapp")
classMyHookSpec:
@hookspecdefon_startup(self) ->None: ...
@hookspecdefon_shutdown(self) ->None: ...
pm=PluginManager(
"config/app.yaml",
app_version="1.0.0", # v0.6.0: enables min_app_version gatingapp_id="myapp", # v0.7.0: enables target_application gating
)
pm.register_hookspecs(MyHookSpec)
@asynccontextmanagerasyncdeflifespan(app: FastAPI):
result=pm.discover_plugins()
pm.mount_routes(app)
pm.call_hook("on_startup")
yieldpm.call_hook("on_shutdown")
pm.deactivate_all()
app=FastAPI(lifespan=lifespan)
# config/app.yamlapp:
name: "MyApp"version: "1.0.0"default_language: "en"plugins:
entry_point_group: "myapp.plugins"enabled:
- "health"
- "audit"

Clone this wiki locally