Plugin Complexity Tiers

All Levels Choose your starting point

DVP CMS is a truth distillation system for AI-generated content. Plugins are evidence suppliers—they verify facts, pull live data, and make content more trustworthy over time. Learn more →

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.

New to DVP CMS? Start with the Simple tier. You can build a working plugin in 15 minutes with just 50 lines of code.

Simple Tier

~50 lines 15 minutes

The absolute minimum needed for a working plugin. One hook, no external dependencies, immediate results.

Single hook implementation
No configuration needed
No external dependencies
Minimal boilerplate
Use when: Learning DVP CMS, building prototypes, simple content transformations, quick experiments.

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

~200 lines 1-2 hours

Production-ready patterns for real-world plugins. API integration, caching, error handling, and evidence submission.

Configuration handling
HTTP client management
Response caching with TTL
Error handling patterns
Evidence submission
Resource cleanup
Use when: Building production plugins, integrating with APIs, caching responses, submitting evidence to the authority system.

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

~500+ lines 2-4 hours

The gold standard reference implementation. Every pattern documented, every hook implemented, every mistake warned against.

All hooks implemented
Authority system integration
Async event handling
Cache eviction strategies
Configuration validation
Exhaustive documentation
"DON'T DO THIS" warnings
Complete test coverage
Use when: Learning best practices, building complex integrations, understanding the full plugin architecture, contributing to the DVP CMS ecosystem.

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.

  1. Start Simple - Build your first plugin in 15 minutes. Understand hooks and lifecycle.
  2. Add Features - When you need API integration, copy patterns from Standard tier.
  3. Master the Platform - When building complex plugins, study the Advanced tier for every pattern and pitfall.
LLM-Friendly: The canonical template is designed to work with AI coding assistants. Point your AI at _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:

Study these plugins to see how patterns apply to specific use cases.