What Plugins Enable
DVP CMS is a truth distillation system. Content regenerates over time, but verified facts persist while unverified filler gets replaced. Plugins are how you feed evidence into this system.
Every plugin you build is an evidence supplier. When your plugin fetches data from an API, verifies a claim, or checks content quality—it submits evidence to the authority system with full provenance. Over regeneration cycles, this evidence accumulates, and your content converges toward ground truth.
What plugins can do:
- Verify facts — Cross-reference claims against Wolfram Alpha, Wikipedia, or Google Fact Check
- Pull live data — Inject weather, stock prices, exchange rates with automatic expiration
- Check quality — Score readability, find broken links, validate image accessibility
- Enforce compliance — Detect PII, scan for copyright issues before publishing
- Trigger workflows — Send Slack alerts, email digests, or webhook notifications
The complexity tier you choose determines how deeply your plugin integrates with the authority system. Simple plugins transform content. Standard plugins fetch and cache external data. Advanced plugins implement the full evidence submission lifecycle.
Complexity Tiers
DVP CMS plugins are organized into three tiers. Start with the tier that matches your needs, then progress as your requirements grow.
Simple Tier
The absolute minimum needed for a working plugin. One hook, no external dependencies, immediate results.
Example: Word Counter Plugin
"""Word Counter Plugin - adds word count to content."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from dvp_cms.plugins.base import Plugin
from dvp_cms.plugins.hookspec import ContentLifecycleHooks
if TYPE_CHECKING:
from dvp_cms.plugins.context import HookContext
class WordCounterPlugin(Plugin, ContentLifecycleHooks):
"""Count words in content before creation."""
name = "word-counter"
version = "1.0.0"
description = "Adds word count to content"
author = "Your Name"
def __init__(self) -> None:
super().__init__()
def dvp_before_content_create(
self,
ctx: HookContext,
content: dict[str, Any],
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Add word count before content is created.
All hooks receive ctx as first parameter for consistency.
Filter hooks must return the modified content (never None).
"""
text = str(content.get("body", ""))
content["word_count"] = len(text.split())
return content # Filter hooks MUST return a value
Template: View the Simple Template source code.
Standard Tier
Production-ready patterns for real-world plugins. API integration, caching, error handling, and evidence submission.
Key Patterns
# FILTER hook - ctx ALWAYS comes first, must return value
def dvp_before_content_create(
self,
ctx: HookContext,
content: dict[str, Any],
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""All hooks receive ctx as first parameter for consistency."""
tenant_id = str(ctx.tenant_id)
config = self.get_config(tenant_id)
api_key = self.get_secrets(tenant_id).require("api_key")
# ... modify content ...
return content # Filter hooks MUST return!
# ACTION hook - ctx also comes first, no return needed
def dvp_content_created(
self,
ctx: HookContext,
content_id: str,
content: dict[str, Any],
metadata: dict[str, Any] | None = None,
) -> None:
"""Side effects only - logging, notifications, etc."""
self.logger.info(f"Content {content_id} created for {ctx.tenant_slug}")
# Caching with TTL (private helper method)
async def _fetch_data(self, text: str) -> dict[str, Any] | None:
cache_key = hashlib.sha256(text.encode()).hexdigest()
cached = self._cache.get(cache_key)
if cached and not cached.is_expired(self._cache_ttl):
return cached.data
# ... fetch fresh data
# Error handling with graceful degradation
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, headers=headers)
except httpx.TimeoutException:
logger.warning("API timeout")
return None # OK for private methods, NOT for filter hooks
except httpx.HTTPStatusError as e:
logger.warning("HTTP error %d", e.response.status_code)
return None
Template: View the Standard Template source code.
Advanced Tier
The gold standard reference implementation. Every pattern documented, every hook implemented, every mistake warned against.
What Makes It Advanced
# Exhaustive documentation
"""
PURPOSE OF THIS FILE
--------------------
This is the GOLD STANDARD reference implementation...
HOOK SIGNATURE PATTERNS
-----------------------
DVP CMS uses a CONSISTENT pattern for ALL hook parameters:
ctx: HookContext is ALWAYS the first parameter after self.
FILTER hooks (modify data): must return a value
def dvp_before_content_create(self, ctx, content, metadata=None) -> dict
ACTION hooks (side effects): no return needed
def dvp_content_created(self, ctx, content_id, content, metadata=None) -> None
GATE hooks (allow/deny): return bool
def dvp_before_content_delete(self, ctx, content_id) -> bool
This consistency makes hooks predictable and easy to learn.
"""
# All hooks with complete docstrings
def dvp_before_content_create(
self,
ctx: HookContext,
content: dict[str, Any],
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Called before content is created.
This is a FILTER HOOK:
- ctx is ALWAYS first (consistent across all hooks)
- MUST return the modified content dict
- Returning None will raise HookError!
Args:
ctx: Hook context with tenant information (always first)
content: Content data being created
metadata: Optional metadata
Returns:
Modified content dict (never None!)
"""
# Inline warnings about common mistakes
# ---------------------------------------------------------------
# DON'T DO THIS: Forgetting to return content
# ---------------------------------------------------------------
#
# WRONG - Will raise HookError!
#
# def dvp_before_content_create(self, ctx, content, metadata=None):
# content["processed"] = True
# # forgot to return - RAISES HookError!
#
# WRONG - Return type is incorrect!
#
# def dvp_before_content_create(self, ctx, content, metadata=None) -> dict | None:
# # Filter hooks must ALWAYS return a value, never None!
#
Template: View the Advanced Template source code. This is the authoritative reference for all DVP CMS plugin patterns.
Choosing Your Tier
| Question | Simple | Standard | Advanced |
|---|---|---|---|
| Do you need external API calls? | No | Yes | Yes |
| Do you need caching? | No | Yes | Yes |
| Do you need authority integration? | No | Optional | Yes |
| Is this for production? | Prototyping | Yes | Yes |
| Time to build | 15 min | 1-2 hours | 2-4 hours |
Progressive Disclosure
The tier system implements progressive disclosure: you learn only what you need, when you need it.
- Start Simple - Build your first plugin in 15 minutes. Understand hooks and lifecycle.
- Add Features - When you need API integration, copy patterns from Standard tier.
- Master the Platform - When building complex plugins, study the Advanced tier for every pattern and pitfall.
_template-canonical/plugin.py and ask it to build plugins following those patterns.
Reference Plugins
In addition to the templates, DVP CMS includes 16 production-quality reference plugins that demonstrate real-world patterns:
- Evidence & Verification: wolfram-fact-checker, wikipedia-citation-linker, google-fact-check
- Data Sources: weather-api, financial-data, exchange-rates
- Content Quality: readability-scorer, broken-link-checker, image-alt-validator
- Analytics: google-analytics-bridge, ab-test-importer
- Compliance: pii-detector, copyright-checker
- Notifications: slack-notifier, telegram-notifier, email-digest
Study these plugins to see how patterns apply to specific use cases.