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:
- ✅ Plugin endpoint is running (
curl http://localhost:PORT) - ✅ Configuration is valid JSON
- ✅
Enabled: truein configuration - ✅ ObjectType matches operation
- ✅ Cesivi server restarted after config change
- ✅ Check Cesivi logs for plugin errors
- ✅ Test plugin manually with curl/Postman
- ✅ 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