Skip to content

Plugin Developer Quick Start Guide

Goal: Create your first Cesivi plugin in 5 minutes!

What You'll Learn: - How plugins work (HTTP hooks) - Creating a "Hello World" plugin - Three practical plugin examples - Debugging tips

Prerequisites: - Cesivi Server running - Basic HTTP server knowledge (Node.js, Python, .NET, etc.) - Text editor

Related Documentation: - Plugin System Guide - Detailed architecture and configuration - API Reference - Cesivi API details


What is a Plugin?

Cesivi plugins are HTTP endpoints that receive notifications when SharePoint objects are accessed or modified.

Plugin Capabilities: - 📝 Audit & Log - Track all SharePoint operations - ✅ Validate - Enforce custom business rules - 🔄 Sync - Replicate changes to external systems - 🔍 Monitor - Track usage patterns and performance - 🎯 Transform - Modify data before/after operations

How Plugins Work:

Client Request → Cesivi Server
                      ↓
                 BEFORE Hook → Your Plugin (HTTP endpoint)
                      ↓
                 Process Request
                      ↓
                 AFTER Hook → Your Plugin (HTTP endpoint)
                      ↓
                 Response to Client

Event Types: - BEFORE - Fired before operation (can modify/abort) - AFTER - Fired after operation (audit/notification only)


Your First Plugin in 5 Minutes

Step 1: Create a Simple HTTP Endpoint (2 min)

Create a file hello-plugin.js (Node.js example):

const http = require('http');

const server = http.createServer((req, res) => {
    console.log('🎉 Plugin called!');
    console.log(`Method: ${req.method}`);
    console.log(`URL: ${req.url}`);

    // Read request body
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
        if (body) {
            const data = JSON.parse(body);
            console.log('Object Type:', data.ObjectType);
            console.log('Operation:', data.Operation);
        }

        // Respond with success
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify({
            Success: true,
            Message: 'Hello from plugin!'
        }));
    });
});

server.listen(5001, () => {
    console.log('🚀 Hello Plugin listening on port 5001');
});

Step 2: Start Your Plugin (30 sec)

node hello-plugin.js

Expected output:

🚀 Hello Plugin listening on port 5001

Step 3: Configure Plugin in Cesivi (1 min)

Edit appsettings.Plugins.json (or appsettings.json):

{
  "Cesivi": {
    "Plugins": [
      {
        "Id": "hello-world",
        "ObjectType": "Microsoft.SharePoint.SPList",
        "EventType": "After",
        "EndpointUrl": "http://localhost:5001",
        "TimeoutMs": 5000,
        "ContinueOnError": true,
        "Enabled": true,
        "Priority": 100,
        "FireAndForget": false
      }
    ]
  }
}

Step 4: Restart Cesivi Server (30 sec)

dotnet run --project Cesivi.Server

Step 5: Trigger Your Plugin (1 min)

Connect with PnP PowerShell and create a list:

Connect-PnPOnline -Url "http://localhost:5000" -UseWebLogin
New-PnPList -Title "TestList" -Template GenericList

Check your plugin console:

🎉 Plugin called!
Method: POST
URL: /
Object Type: Microsoft.SharePoint.SPList
Operation: Save

🎉 Congratulations! Your first plugin is working!


Example 1: Request Logger Plugin

Use Case: Log all SharePoint list operations with timestamps

Plugin Code (Python with Flask)

Create logger-plugin.py:

from flask import Flask, request, jsonify
from datetime import datetime
import json

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def log_operation():
    timestamp = datetime.now().isoformat()

    # Parse incoming plugin event
    data = request.get_json() or {}

    object_type = data.get('ObjectType', 'Unknown')
    operation = data.get('Operation', 'Unknown')
    context = data.get('Context', {})

    # Log to console
    print(f"[{timestamp}] {operation} on {object_type}")
    print(f"  Web App: {context.get('WebAppName', 'N/A')}")
    print(f"  Site: {context.get('SiteName', 'N/A')}")
    print(f"  List: {context.get('ListName', 'N/A')}")

    # Log to file
    with open('plugin-audit.log', 'a') as f:
        f.write(f"{timestamp} | {operation} | {object_type} | {json.dumps(context)}\n")

    # Respond
    return jsonify({
        'Success': True,
        'Message': f'Logged {operation} operation'
    })

if __name__ == '__main__':
    print('📊 Audit Logger Plugin starting on port 5002...')
    app.run(host='0.0.0.0', port=5002)

Configuration

{
  "Id": "audit-logger",
  "ObjectType": "Microsoft.SharePoint.*",
  "EventType": "After",
  "EndpointUrl": "http://localhost:5002",
  "TimeoutMs": 5000,
  "ContinueOnError": true,
  "Enabled": true
}

Run Plugin

pip install flask
python logger-plugin.py

What It Does

  • ✅ Logs all SharePoint operations (lists, items, files)
  • ✅ Captures timestamp, operation type, context
  • ✅ Writes to console and file
  • ✅ Non-blocking (ContinueOnError: true)

Use Cases: - Compliance auditing - Usage analytics - Debugging SharePoint operations - Security monitoring


Example 2: Validation Plugin (BEFORE Hook)

Use Case: Prevent list creation without required metadata

Plugin Code (.NET Web API)

Create ValidationPlugin/Controllers/ValidateController.cs:

using Microsoft.AspNetCore.Mvc;
using System.Text.Json;

namespace ValidationPlugin.Controllers;

[ApiController]
[Route("/")]
public class ValidateController : ControllerBase
{
    private readonly ILogger<ValidateController> _logger;

    public ValidateController(ILogger<ValidateController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    [HttpGet]
    public IActionResult ValidateOperation([FromBody] JsonElement? body)
    {
        if (body == null || body.Value.ValueKind == JsonValueKind.Undefined)
        {
            return Ok(new { Success = true });
        }

        var data = body.Value;

        // Get operation details
        var operation = data.GetProperty("Operation").GetString();
        var objectType = data.GetProperty("ObjectType").GetString();

        // Only validate list creation
        if (operation == "Save" && objectType == "Microsoft.SharePoint.SPList")
        {
            // Check if Data contains required fields
            if (data.TryGetProperty("Data", out var listData))
            {
                var title = listData.TryGetProperty("Title", out var t) ?
                    t.GetString() : "";
                var description = listData.TryGetProperty("Description", out var d) ?
                    d.GetString() : "";

                // Validation rule: Lists must have descriptions
                if (string.IsNullOrWhiteSpace(description))
                {
                    _logger.LogWarning($"Blocked list creation: '{title}' has no description");

                    return Ok(new
                    {
                        Success = false,
                        Abort = true,  // ABORT the operation
                        Message = "Lists must have a description for compliance"
                    });
                }
            }
        }

        // Allow operation
        return Ok(new { Success = true });
    }
}

Configuration (BEFORE Hook)

{
  "Id": "list-validator",
  "ObjectType": "Microsoft.SharePoint.SPList",
  "EventType": "Before",
  "EndpointUrl": "http://localhost:5003",
  "TimeoutMs": 3000,
  "ContinueOnError": false,
  "Enabled": true
}

Key Differences: - EventType: "Before" (runs before operation) - ContinueOnError: false (enforce validation) - Returns Abort: true to block operation

What It Does

  • ✅ Validates lists before creation
  • ✅ Enforces business rules (description required)
  • ✅ Blocks non-compliant operations
  • ✅ Returns helpful error messages

Use Cases: - Enforce naming conventions - Require metadata fields - Prevent duplicate items - Access control validation


Example 3: Environment-Based Configuration

Use Case: Different behavior for dev/staging/production

Plugin Code (Node.js with Environment Variables)

Create env-plugin.js:

const express = require('express');
const app = express();

// Read environment configuration
const PLUGIN_MODE = process.env.CESIVI_PLUGIN_MODE || 'production';
const ENABLE_VERBOSE = process.env.CESIVI_PLUGIN_VERBOSE === 'true';
const WEBHOOK_URL = process.env.CESIVI_WEBHOOK_URL;

console.log(`🔧 Plugin Mode: ${PLUGIN_MODE}`);
console.log(`📝 Verbose Logging: ${ENABLE_VERBOSE}`);

app.use(express.json());

app.all('/', (req, res) => {
    const data = req.body || {};

    // Development mode: Log everything
    if (PLUGIN_MODE === 'development' || ENABLE_VERBOSE) {
        console.log('📥 Plugin Event:', {
            ObjectType: data.ObjectType,
            Operation: data.Operation,
            Context: data.Context,
            Timestamp: new Date().toISOString()
        });
    }

    // Production mode: Only log errors
    if (PLUGIN_MODE === 'production') {
        if (data.Operation === 'Delete') {
            console.warn('⚠️  DELETE operation detected!');

            // Send to external webhook in production
            if (WEBHOOK_URL) {
                sendToWebhook(WEBHOOK_URL, data);
            }
        }
    }

    // Staging mode: Sample 10% of operations
    if (PLUGIN_MODE === 'staging') {
        if (Math.random() < 0.1) {
            console.log('📊 Sample:', data.Operation, data.ObjectType);
        }
    }

    res.json({ Success: true });
});

function sendToWebhook(url, data) {
    // Implementation: POST to webhook
    console.log(`🔔 Webhook: ${url}`);
}

const PORT = process.env.PORT || 5004;
app.listen(PORT, () => {
    console.log(`🌍 Environment Plugin on port ${PORT}`);
});

Configuration (with Environment Variables)

In appsettings.Plugins.json:

{
  "Id": "env-config",
  "ObjectType": "Microsoft.SharePoint.*",
  "EventType": "After",
  "EndpointUrl": "http://localhost:5004",
  "Headers": {
    "X-Environment": "{CESIVI_ENVIRONMENT}",
    "X-API-Key": "{CESIVI_API_KEY}"
  },
  "TimeoutMs": 5000,
  "ContinueOnError": true,
  "Enabled": true
}

Run Plugin with Environment Variables

# Development
export CESIVI_PLUGIN_MODE=development
export CESIVI_PLUGIN_VERBOSE=true
node env-plugin.js

# Production
export CESIVI_PLUGIN_MODE=production
export CESIVI_WEBHOOK_URL=https://alerts.company.com/webhook
node env-plugin.js

What It Does

  • ✅ Different behavior per environment
  • ✅ Verbose logging in dev, silent in prod
  • ✅ Sampling in staging
  • ✅ External webhooks in production

Use Cases: - Environment-specific logging - Feature flags - Different integrations per environment - Cost optimization (sampling)


Debugging Tips

Plugin Not Being Called

Check plugin configuration:

{
  "Enabled": true,  // ✅ Must be true
  "ObjectType": "Microsoft.SharePoint.SPList",  // ✅ Must match
  "EventType": "After"  // ✅ Before or After
}

Verify endpoint is running:

curl http://localhost:5001
# Should respond (even with error is OK)

Check Cesivi logs:

[INFO] Loading plugins from configuration...
[INFO] Registered plugin: hello-world

Timeout Errors

Increase timeout:

{
  "TimeoutMs": 10000  // 10 seconds instead of 5
}

Make plugin faster: - Use async I/O - Don't block on slow operations - Return quickly, process in background

Plugin Errors Break Server

Use ContinueOnError:

{
  "ContinueOnError": true  // Server continues even if plugin fails
}

For validation plugins (must succeed):

{
  "EventType": "Before",
  "ContinueOnError": false  // Fail if plugin fails
}

Wildcard Not Matching

Test with specific type first:

{
  "ObjectType": "Microsoft.SharePoint.SPList"  // Specific
}

Then expand:

{
  "ObjectType": "Microsoft.SharePoint.*"  // All SharePoint objects
}

Headers Not Working

Environment variables must be set BEFORE starting server:

# Windows
set CESIVI_API_KEY=secret123
dotnet run --project Cesivi.Server

# Linux/Mac
export CESIVI_API_KEY=secret123
dotnet run --project Cesivi.Server

Check header syntax:

{
  "Headers": {
    "X-API-Key": "{CESIVI_API_KEY}"  // ✅ Correct
  }
}

Testing Plugins Without Cesivi

Send test request manually:

curl -X POST http://localhost:5001 \
  -H "Content-Type: application/json" \
  -d '{
    "ObjectType": "Microsoft.SharePoint.SPList",
    "Operation": "Save",
    "Context": {
      "WebAppName": "Default",
      "SiteName": "RootSite",
      "ListName": "TestList"
    },
    "Data": {
      "Title": "My List",
      "Description": "Test description"
    }
  }'

Expected response:

{
  "Success": true,
  "Message": "Hello from plugin!"
}


Plugin Request/Response Format

Request Format (from Cesivi to Plugin)

{
  "ObjectType": "Microsoft.SharePoint.SPList",
  "Operation": "Save",
  "Context": {
    "WebAppName": "Default",
    "SiteName": "RootSite",
    "ListName": "Documents",
    "ItemId": "123"
  },
  "Data": {
    "Title": "My List",
    "Description": "Custom list",
    "Template": "GenericList"
  },
  "EventType": "Before"
}

Response Format (from Plugin to Cesivi)

Success (allow operation):

{
  "Success": true,
  "Message": "Operation validated"
}

Block operation (BEFORE hook only):

{
  "Success": false,
  "Abort": true,
  "Message": "Validation failed: Description required"
}

Modify data (BEFORE hook only):

{
  "Success": true,
  "ModifiedData": {
    "Title": "My List",
    "Description": "Auto-generated description",
    "Template": "GenericList"
  }
}


Plugin Ideas

Audit & Compliance

  • Audit trail logger
  • GDPR compliance tracking
  • Change history recorder
  • Access log analyzer

Integration

  • Sync to database
  • Post to message queue
  • Trigger workflows
  • Update cache/search index

Validation & Security

  • Naming convention enforcer
  • Permission validator
  • Quota checker
  • Malware scanner (for files)

Monitoring & Analytics

  • Performance metrics
  • Usage statistics
  • Error tracking
  • Capacity planning

Development & Testing

  • Mock data generator
  • Test data validator
  • Debug logger
  • Performance profiler

Advanced Features Quick Reference

Fire-and-Forget After Hooks

Non-blocking background processing for After hooks. Set "FireAndForget": true to move plugin calls off the request path. Failed calls retry with exponential backoff (MaxRetries / RetryDelayMs).

{
  "Id": "async-audit",
  "ObjectType": "Microsoft.SharePoint.*",
  "EventType": "After",
  "EndpointUrl": "http://audit:5000/log",
  "FireAndForget": true,
  "MaxRetries": 3,
  "RetryDelayMs": 1000
}

Plugin Metrics

Monitor plugin health at GET /_api/diagnostics/plugins. Returns per-plugin invocation counts, success/failure rates, average response times, and background queue statistics.

Runtime Management API

Manage plugins without restarting the server via /_admin/plugins: - GET /_admin/plugins — list all plugins - POST /_admin/plugins — register a new plugin - PUT /_admin/plugins/{id} — update a plugin - DELETE /_admin/plugins/{id} — remove a runtime plugin - POST /_admin/plugins/{id}/test — test connectivity

See Plugin System Guide for full details.

Priority Ordering

Control execution order with the Priority property (default: 100). Lower values execute first. Example: set validation to 10 and audit to 200.

Hot-Reload

Edit appsettings.json and save — plugin configuration reloads automatically without server restart. Runtime-added plugins (via admin API) are preserved during reload.


Next Steps

1. Learn More

Detailed Documentation: - Plugin System Guide - Full architecture details - API Reference - Cesivi API - Configuration Guide - Environment variables

2. Advanced Topics

Multi-Plugin Chains: - Register multiple plugins for same object type - Execution order and dependencies - Data transformation pipelines

Error Handling: - Retry strategies - Circuit breakers - Fallback behaviors

Performance: - Async plugin processing - Caching strategies - Batch operations

3. Share Your Plugin

Community Plugins: - Create plugin repository - Document your plugin - Share configuration examples - Contribute to ecosystem

4. Plugin Testing

Unit Testing:

// Example: Mocha test for plugin
describe('Hello Plugin', () => {
  it('should respond with success', async () => {
    const response = await fetch('http://localhost:5001', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ObjectType: 'Microsoft.SharePoint.SPList',
        Operation: 'Save'
      })
    });
    const data = await response.json();
    expect(data.Success).to.be.true;
  });
});


Common Use Cases

Use Case 1: Audit All List Deletions

{
  "Id": "delete-auditor",
  "ObjectType": "Microsoft.SharePoint.SPList",
  "EventType": "After",
  "EndpointUrl": "http://audit-server:5000/deletions",
  "Headers": {
    "X-Audit-Category": "DELETION",
    "X-Severity": "HIGH"
  }
}

Use Case 2: Validate File Uploads

{
  "Id": "file-validator",
  "ObjectType": "Microsoft.SharePoint.SPFile",
  "EventType": "Before",
  "EndpointUrl": "http://av-scanner:5000/scan",
  "TimeoutMs": 30000,
  "ContinueOnError": false
}

Use Case 3: Sync to External Database

{
  "Id": "db-sync",
  "ObjectType": "Microsoft.SharePoint.SPListItem",
  "EventType": "After",
  "EndpointUrl": "http://sync-service:5000/items",
  "Headers": {
    "X-DB-Connection": "{DB_CONNECTION_STRING}"
  }
}

Troubleshooting Checklist

Before asking for help:

  1. ✅ Plugin endpoint is running (curl http://localhost:PORT)
  2. ✅ Configuration is valid JSON
  3. Enabled: true in configuration
  4. ✅ ObjectType matches operation
  5. ✅ Cesivi server restarted after config change
  6. ✅ Check Cesivi logs for plugin errors
  7. ✅ Test plugin manually with curl/Postman
  8. ✅ Environment variables set before server start

Quick Reference Card

Plugin Configuration Template

{
  "Id": "my-plugin",
  "ObjectType": "Microsoft.SharePoint.SPList",
  "EventType": "After",
  "EndpointUrl": "http://localhost:5001",
  "Headers": {
    "X-Custom-Header": "value",
    "X-API-Key": "{ENV_VAR}"
  },
  "TimeoutMs": 5000,
  "ContinueOnError": true,
  "Enabled": true,
  "Priority": 100,
  "FireAndForget": false,
  "MaxRetries": 3,
  "RetryDelayMs": 1000
}

Plugin Response Template

{
  "Success": true,
  "Abort": false,
  "Message": "Optional message",
  "ModifiedData": null
}

Event Types

Event When Can Modify Can Abort
Before Before operation ✅ Yes ✅ Yes
After After operation ❌ No ❌ No

Document Version: 2.0 Last Updated: 2026-03-21 Cesivi Version: 1.0+

Need Help? Check the Plugin System Guide or Troubleshooting Guide