Cesivi Solution Packages (CSP)¶
This guide explains how to create, package, and deploy Cesivi Solution Packages (CSP) — the Cesivi extensibility format for adding custom web parts and features to a Cesivi server.
Table of Contents¶
- Overview
- CSP Format Specification
- Property Types
- Template Rendering
- Asset Serving
- Walkthrough: Create Your First CSP Package
- REST API Reference
- WSP Compatibility
1. Overview¶
Cesivi Solution Packages (CSP) are ZIP-based archives that contain:
- A manifest describing the solution (name, version, web part list)
- One or more web part definitions (JSON schema + HTML/CSS/JS templates)
Once uploaded to a Cesivi server, a CSP can be deployed to make its web parts available in the web part gallery. Deployed web parts can then be added to wiki pages and configured per-instance.
CSP vs WSP¶
| Format | Use When | Assembly Loading |
|---|---|---|
| CSP (Cesivi Solution Package) | Adding custom web parts with HTML/JS/CSS templates | Not applicable |
| WSP (SharePoint Solution Package) | Migrating from SharePoint; activating CAML-defined features | Not supported — see WSP Compatibility |
Use CSP for all new development. CSP packages are ZIP archives and require no special tooling to create.
Sample Packages¶
Cesivi ships with three ready-to-use sample packages that demonstrate all CSP capabilities:
- Clock Widget — Live digital clock with configurable timezone and 12h/24h format
- Announcement Banner — Configurable banner with title, message, background color, link, and dismiss button
- Weather Info — Weather display widget with mock data or configurable API URL
To install them: open the Solutions Gallery (/_layouts/15/solutions.aspx) and click Install Sample Packages.
2. CSP Format Specification¶
A CSP package is a ZIP file with a .csp extension (.zip is also accepted). The archive must contain a manifest.json at the root level, plus a subdirectory for each web part.
Minimum Directory Structure¶
my-solution.csp (ZIP archive)
├── manifest.json ← Required: solution metadata
└── webparts/
└── my-webpart/
├── webpart.json ← Required: web part schema
├── render.html ← Required: display template
├── style.css ← Optional: scoped styles
├── script.js ← Optional: client-side logic
└── edit.html ← Optional: custom property editor
Multiple web parts are supported — add more subdirectories under webparts/:
my-solution.csp
├── manifest.json
└── webparts/
├── widget-a/
│ ├── webpart.json
│ └── render.html
└── widget-b/
├── webpart.json
├── render.html
├── style.css
└── script.js
Web part folders can be named anything (no spaces required) and can be nested arbitrarily deep. The path in manifest.json → webparts array must match the actual folder path.
manifest.json Schema¶
The manifest.json file at the ZIP root describes the solution.
{
"id": "12345678-abcd-4000-8000-000000000001",
"name": "My Solution",
"version": "1.0.0",
"description": "A short description of what this solution provides.",
"author": "Your Name or Team",
"webparts": [
"webparts/my-webpart"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (GUID) | Yes | Unique identifier for this solution. Must be a valid GUID. Use a new GUID for each solution you create. |
name |
string | Yes | Display name shown in the Solutions Gallery. |
version |
string | No | Semantic version string (e.g. "1.0.0"). Parsed as System.Version. |
description |
string | No | Human-readable description. Shown in the solution details panel. |
author |
string | No | Author or team name. |
webparts |
string[] | No | Array of folder paths (relative to ZIP root) to web part subdirectories. Each path must point to a directory containing a webpart.json. |
features |
string[] | No | Reserved for future use. Ignored by current parser. |
Rules:
- id must be a parseable GUID or the upload will fail with a 400 Bad Request.
- name must not be empty or whitespace.
- If webparts is omitted or empty, the solution uploads successfully but adds no web parts to the gallery.
webpart.json Schema¶
Each web part subdirectory must contain a webpart.json describing the web part and its configurable properties.
{
"id": "12345678-abcd-4000-8000-000000000002",
"name": "my-webpart",
"title": "My Web Part",
"description": "Displays something useful.",
"category": "My Category",
"iconClass": "bi-star",
"properties": [
{
"name": "message",
"title": "Message",
"type": "string",
"default": "Hello, World!",
"required": false
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (GUID) | Yes | Unique identifier for this web part. Must be a valid GUID. Different from the solution ID. |
name |
string | No | Internal name (used as web part folder name if title omitted). |
title |
string | No | Display name in the web part gallery. Falls back to name if omitted. |
description |
string | No | Short description shown in the gallery picker. |
category |
string | No | Category grouping in the gallery. Defaults to "Custom". |
iconClass |
string | No | Bootstrap Icons class for the gallery icon (e.g. "bi-star", "bi-clock"). Defaults to "bi-puzzle". |
properties |
array | No | Configurable properties. See Property Types. |
Rules:
- id must be a parseable GUID or the web part is skipped (a warning is logged, but the rest of the solution parses successfully).
- If webpart.json is missing at the declared path, the web part is skipped with a warning.
3. Property Types¶
Properties listed in webpart.json → properties define the configuration UI for each web part instance. Each property has a name, title, type, optional default, and optional required flag.
Property Definition Schema¶
{
"name": "myProp",
"title": "Human-readable label",
"type": "string",
"default": "default value",
"required": false
}
| Field | Description |
|---|---|
name |
Internal name. Used as the {{placeholder}} name in templates. Must be a valid identifier (letters, digits, underscores). |
title |
Label shown in the property editor panel. |
type |
One of the supported types (see table below). |
default |
Default value as a string. Applied when the web part is first added to a page. |
required |
Whether the property must be filled in. Informational only — the server does not enforce this. |
Supported Property Types¶
| Type | Description | Editor UI | Notes |
|---|---|---|---|
string |
Plain text value | <input type="text"> |
Default type if omitted |
boolean |
True/false flag | <input type="checkbox"> |
Stored as "true" or "false" string; use {{#if propName}} in templates |
number |
Numeric value | <input type="text"> |
No numeric validation in auto-generated editor; validated by your own edit.html |
color |
Color value | <input type="text"> |
Intended for hex colors or CSS color names; no color picker in auto-generated editor |
url |
URL string | <input type="text"> |
No URL validation in auto-generated editor |
Note: The
typefield affects how properties are rendered in the auto-generated editor. When you provide a customedit.html, you control the editor entirely and can use any input types.
Built-in Template Variables¶
In addition to your custom properties, the following variables are always available in templates:
| Variable | Value |
|---|---|
{{WebPartInstanceId}} |
Unique ID of this web part instance on the page |
{{Title}} |
Current title of the web part (editable) |
{{Description}} |
Current description of the web part |
{{WebUrl}} |
Server-relative URL of the current web (e.g. /) |
{{PageUrl}} |
Server-relative URL of the current page |
{{CurrentUser}} |
Login name of the current user |
4. Template Rendering¶
Templates are HTML files processed server-side before being sent to the browser.
render.html¶
The render.html file is the display template — rendered every time the web part appears on a page.
Cesivi loads this file from the CSP assets store and processes two types of markup:
1. Simple Placeholders: {{propName}}¶
<p>Message: {{message}}</p>
<a href="{{linkUrl}}">Click here</a>
All placeholder values are HTML-encoded automatically (e.g. < → <). This prevents XSS from user-controlled property values. If a placeholder name is not recognized, it is left as-is in the output.
2. Conditional Blocks: {{#if propName}}...{{/if}}¶
{{#if showLink}}<a href="{{linkUrl}}">More info</a>{{/if}}
The block is included when the property value is truthy: any non-empty string that is not "false" or "0". For boolean properties, set to "true" to include the block.
Conditional blocks are evaluated before placeholder substitution, so you can use {{placeholder}} inside them.
Nested {{#if}} blocks are not supported in the current version.
style.css¶
When style.css is present, Cesivi:
1. Wraps all rules in a generated scope class: .csp-wp-{webPartId} (prevents global CSS pollution)
2. Injects the scoped stylesheet as an inline <style> block after the web part HTML
Write your CSS as if scoping to .csp-wp-{id} is automatic:
/* Your CSS — will be scoped automatically */
.my-widget {
border: 1px solid #dee2e6;
padding: 16px;
}
.my-widget h2 {
font-size: 1.2rem;
}
Do not include the scope prefix yourself; Cesivi adds it.
script.js¶
When script.js is present, Cesivi injects it as an IIFE (Immediately Invoked Function Expression) after the web part HTML, with a wpContext parameter:
(function(wpContext) {
// Your script.js code runs here
// wpContext.id → web part definition ID (GUID string)
// wpContext.title → web part title
// Example: find your DOM root
var root = document.querySelector('.my-widget');
if (!root) return;
// Start your logic
setInterval(function() {
root.querySelector('.time').textContent = new Date().toLocaleTimeString();
}, 1000);
})(/* wpContext provided by Cesivi */);
The outer IIFE prevents variable leakage to the global scope. Multiple web parts on the same page each get their own IIFE execution.
Important: script.js should be self-contained. Do not rely on global variables from other scripts. Since each web part's IIFE runs independently, use the DOM structure of render.html to find your elements.
edit.html (Optional)¶
If you provide an edit.html file, it replaces the auto-generated property editor panel for this web part. Use this for rich editing controls (color pickers, dropdowns, multi-line text, etc.).
The edit.html file supports the same {{propName}} placeholder syntax as render.html. The current property values are substituted automatically.
To save properties from a custom edit.html, your form inputs must have name attributes matching your property names:
<!-- edit.html -->
<div class="form-group">
<label>Background Color</label>
<input type="color" name="backgroundColor" value="{{backgroundColor}}">
</div>
<div class="form-group">
<label>Title</label>
<input type="text" name="title" value="{{title}}">
</div>
Cesivi reads all form[name] values on save and maps them to the matching property names.
Rendering Pipeline Summary¶
1. Load render.html from CSP assets store
2. Process {{#if propName}}...{{/if}} conditional blocks
3. Replace {{propName}} placeholders with HTML-encoded property values
4. Wrap output in <div class="csp-wp-{id}">
5. Append scoped <style> block (from style.css, if present)
6. Append IIFE <script> block (from script.js, if present)
5. Asset Serving¶
All CSP asset files are served by the Cesivi REST API at:
GET /_api/solutions/{solutionId}/assets/{assetPath}
For example, if your solution ID is 12345678-... and your web part folder is webparts/my-webpart:
| File | URL |
|---|---|
render.html |
/_api/solutions/12345678-.../assets/webparts/my-webpart/render.html |
script.js |
/_api/solutions/12345678-.../assets/webparts/my-webpart/script.js |
style.css |
/_api/solutions/12345678-.../assets/webparts/my-webpart/style.css |
webpart.json |
/_api/solutions/12345678-.../assets/webparts/my-webpart/webpart.json |
Content Types¶
| Extension | Content-Type |
|---|---|
.html |
text/html |
.js |
application/javascript |
.css |
text/css |
.json |
application/json |
| other | application/octet-stream |
Security¶
- Path traversal attempts (containing
..) return400 Bad Request - Asset serving is only available for CSP solutions (
PackageType == "CSP"); WSP solutions return404 - The solution must exist in the store; deleted solutions return
404
Files Extracted¶
Asset extraction stores the following file types from the ZIP:
.html.js.css.json(includingwebpart.jsonandmanifest.json)
Other file types (images, fonts, binaries) are not extracted and cannot be served. If you need images, reference external URLs or embed them as base64 data URIs in your HTML/CSS.
6. Walkthrough: Create Your First CSP Package¶
This walkthrough creates a minimal "Hello World" web part with a configurable greeting message.
Step 1: Create the Files¶
Create the following directory structure on your machine:
hello-world/
├── manifest.json
└── webparts/
└── hello/
├── webpart.json
└── render.html
manifest.json:
{
"id": "a1b2c3d4-0001-4000-8000-000000000001",
"name": "Hello World",
"version": "1.0.0",
"description": "A simple greeting web part.",
"author": "Your Name",
"webparts": ["webparts/hello"]
}
Generate a fresh GUID for your own package — do not reuse this example GUID.
webparts/hello/webpart.json:
{
"id": "a1b2c3d4-0002-4000-8000-000000000001",
"name": "hello",
"title": "Hello World",
"description": "Displays a configurable greeting.",
"category": "Custom",
"iconClass": "bi-hand-wave",
"properties": [
{
"name": "greeting",
"title": "Greeting Message",
"type": "string",
"default": "Hello, World!"
},
{
"name": "showDate",
"title": "Show Today's Date",
"type": "boolean",
"default": "true"
}
]
}
webparts/hello/render.html:
<div class="hw-box">
<p class="hw-greeting">{{greeting}}</p>
{{#if showDate}}<p class="hw-date" id="hw-date-{{WebPartInstanceId}}"></p>{{/if}}
</div>
Step 2: (Optional) Add Styles and Script¶
webparts/hello/style.css:
.hw-box {
border: 2px solid #0078d4;
border-radius: 6px;
padding: 16px 20px;
font-family: sans-serif;
max-width: 400px;
}
.hw-greeting {
font-size: 1.4rem;
font-weight: 600;
color: #0078d4;
margin: 0 0 8px;
}
.hw-date {
color: #555;
font-size: 0.9rem;
margin: 0;
}
webparts/hello/script.js:
(function(wpContext) {
var dateEl = document.getElementById('hw-date-' + wpContext.id);
if (dateEl) {
dateEl.textContent = 'Today is ' + new Date().toLocaleDateString();
}
})(/* wpContext */);
Note: When script.js is injected by Cesivi,
wpContextis provided automatically. You can referencewpContext.idto find instance-specific elements using the{{WebPartInstanceId}}placeholder in your HTML.
Wait — the WebPartInstanceId is injected server-side as a placeholder value, but wpContext.id is the web part definition ID. To make instance-specific element targeting work correctly, use {{WebPartInstanceId}} in the HTML and store it in a data attribute:
Revised render.html:
<div class="hw-box" data-instance="{{WebPartInstanceId}}">
<p class="hw-greeting">{{greeting}}</p>
{{#if showDate}}<p class="hw-date"></p>{{/if}}
</div>
Revised script.js:
(function(wpContext) {
var box = document.querySelector('.hw-box[data-instance]');
if (!box) return;
var dateEl = box.querySelector('.hw-date');
if (dateEl) {
dateEl.textContent = 'Today is ' + new Date().toLocaleDateString();
}
})(/* wpContext */);
Step 3: Package as ZIP¶
Zip the contents of the hello-world/ directory. The ZIP must have manifest.json at its root (not inside a subdirectory).
On Windows (PowerShell):
# From inside the hello-world/ directory
Compress-Archive -Path manifest.json, webparts -DestinationPath hello-world.csp
On Linux/macOS:
# From inside the hello-world/ directory
zip -r hello-world.csp manifest.json webparts/
Verify the structure:
hello-world.csp
├── manifest.json ← MUST be at root
└── webparts/
└── hello/
├── webpart.json
├── render.html
├── style.css
└── script.js
Step 4: Upload via Solutions Gallery¶
- Navigate to
/_layouts/15/solutions.aspxin your Cesivi instance - Click Upload Solution
- Select
hello-world.csp - Verify the package details shown (name, version, web part count)
Step 5: Deploy¶
After upload, the solution is in the gallery but its web parts are not yet available.
Click Deploy on the solution row, or use the REST API:
curl -X POST http://localhost:5000/_api/solutions/{solutionId}/deploy
Step 6: Add to a Wiki Page¶
- Navigate to any wiki page (e.g.
/_layouts/15/wikiedit.aspx?page=...) - Click Add Web Part (the
+button in the ribbon or page) - Find Hello World in the Custom category
- Click to insert
- Use the gear icon or Edit Web Part to configure the greeting and toggle the date display
Step 7: (Optional) Retract and Delete¶
To remove the web parts from the gallery without losing the solution:
curl -X POST http://localhost:5000/_api/solutions/{solutionId}/retract
To remove the solution entirely:
curl -X DELETE http://localhost:5000/_api/solutions/{solutionId}
7. REST API Reference¶
Base URL: /_api/solutions
All endpoints return JSON. Error responses have the shape {"error": "message"}.
Upload a Solution¶
POST /_api/solutions/upload
Content-Type: multipart/form-data
Upload a .csp, .zip, or .wsp file. The file field name must be wspFile.
Request (multipart form):
| Field | Type | Description |
|---|---|---|
wspFile |
File | The solution package file. Extension must be .csp, .zip, or .wsp. |
Response 200 OK:
{
"solutionId": "12345678-abcd-4000-8000-000000000001",
"title": "Hello World",
"description": "A simple greeting web part.",
"fileName": "hello-world.csp",
"version": "1.0.0",
"packageType": "CSP",
"isDeployed": false,
"parsedAt": "2026-04-07T10:00:00Z",
"features": [],
"webPartCount": 1,
"webParts": [
{
"id": "a1b2c3d4-0002-4000-8000-000000000001",
"name": "hello",
"title": "Hello World",
"description": "Displays a configurable greeting.",
"category": "Custom",
"iconClass": "bi-hand-wave",
"webPartFolder": "webparts/hello",
"properties": [
{ "name": "greeting", "title": "Greeting Message", "type": "string", "required": false, "default": "Hello, World!" },
{ "name": "showDate", "title": "Show Today's Date", "type": "boolean", "required": false, "default": "true" }
]
}
]
}
Errors:
| Status | Condition |
|---|---|
400 |
No file, wrong extension, or parse error (JSON invalid, missing required fields, GUID format wrong) |
PowerShell example:
$result = Invoke-RestMethod -Uri "http://localhost:5000/_api/solutions/upload" `
-Method Post `
-Form @{ wspFile = Get-Item "hello-world.csp" }
$solutionId = $result.solutionId
curl example:
curl -X POST http://localhost:5000/_api/solutions/upload \
-F "wspFile=@hello-world.csp"
List All Solutions¶
GET /_api/solutions
Response 200 OK:
{
"solutions": [
{
"solutionId": "...",
"title": "Hello World",
"description": "...",
"fileName": "hello-world.csp",
"version": "1.0.0",
"featureCount": 0,
"isDeployed": true,
"parsedAt": "2026-04-07T10:00:00Z",
"packageType": "CSP",
"webPartCount": 1,
"webParts": [ ... ]
}
]
}
For CSP solutions, webParts contains the full property schema (same structure as the upload response). For WSP solutions, webParts is always an empty array.
Get Solution Details¶
GET /_api/solutions/{solutionId}
Response 200 OK: Same shape as upload response, with current isDeployed state.
Errors:
| Status | Condition |
|---|---|
404 |
Solution not found |
Get Solution Asset¶
GET /_api/solutions/{solutionId}/assets/{assetPath}
Serves a file from the solution's extracted assets directory.
Parameters:
| Parameter | Description |
|---|---|
solutionId |
The solution GUID |
assetPath |
Relative path within the solution assets (e.g. webparts/hello/render.html) |
Response: The file content with appropriate Content-Type.
Errors:
| Status | Condition |
|---|---|
400 |
Path contains .. or is absolute (path traversal protection) |
404 |
Solution not found, not a CSP, or asset file not found |
Deploy a Solution¶
POST /_api/solutions/{solutionId}/deploy
Marks the solution as deployed and activates its features (WSP) / makes its web parts available in the gallery (CSP).
Response 200 OK:
{
"message": "Solution deployed successfully",
"solutionId": "12345678-..."
}
Errors:
| Status | Condition |
|---|---|
404 |
Solution not found |
Retract a Solution¶
POST /_api/solutions/{solutionId}/retract
Marks the solution as not deployed. Web parts from this solution are no longer available in the gallery. Existing web part instances on pages continue to render (with a "Template not found" message if assets are also deleted).
Response 200 OK:
{
"message": "Solution retracted successfully",
"solutionId": "12345678-..."
}
Delete a Solution¶
DELETE /_api/solutions/{solutionId}
Permanently removes the solution from the gallery and deletes its extracted assets.
Response 200 OK:
{
"message": "Solution deleted successfully",
"solutionId": "12345678-..."
}
Deletion succeeds even if the solution does not exist (idempotent).
PowerShell Cmdlets¶
The CeSivi.PowerShell module includes 5 cmdlets for solution management:
| Cmdlet | Description |
|---|---|
Get-PnPApp |
List all solutions |
Add-PnPApp |
Upload a solution package |
Install-PnPApp |
Deploy a solution |
Uninstall-PnPApp |
Retract a solution |
Remove-PnPApp |
Delete a solution |
See POWERSHELL_CMDLETS.md for full syntax.
8. WSP Compatibility¶
Cesivi can parse and store WSP (SharePoint Solution Package) files. WSP packages are CAB archives that may contain:
manifest.xml— solution manifestFeature.xml— feature definitions- CAML element manifests (list definitions, content types, etc.)
- .NET assemblies (DLLs)
What Is Supported¶
| WSP Capability | Cesivi Support |
|---|---|
Upload and parse manifest.xml |
✅ |
Parse Feature.xml (Id, Title, Description, Scope) |
✅ |
| Display features in Solutions Gallery | ✅ |
| Feature activation / deactivation lifecycle | ✅ (marks as activated; feature elements not processed) |
| CAML list definitions / content types | ✅ (parsed but not instantiated) |
| Retract and delete | ✅ |
What Is NOT Supported¶
| WSP Capability | Status |
|---|---|
| Loading .NET assemblies from the WSP | Not supported — see below |
| Executing assembly event receivers | Not supported |
| Provisioning lists from CAML definitions | Not supported |
| Custom site definitions from WSP | Not supported |
Why Assembly Loading Is Not Supported¶
WSP packages built for SharePoint contain .NET Framework DLLs targeting the old .NET Framework runtime. Cesivi runs on .NET 10 (modern .NET runtime), which cannot load or execute .NET Framework 4.x assemblies in the same process.
This is a hard platform limitation — not a Cesivi implementation choice. It cannot be worked around without running a separate .NET Framework process.
If you need custom server-side logic in Cesivi, use the NuGet-based plugin system instead. Plugins run as .NET 10 assemblies in-process and have full access to Cesivi's extensibility hooks.
Using WSP with Cesivi¶
WSP packages are useful for: 1. Inspecting your existing SharePoint solutions — upload to see which features they contain 2. Feature lifecycle testing — the deploy/retract lifecycle works the same as real SharePoint 3. Migration reference — use the feature list to understand what needs to be reimplemented as CSP or NuGet plugins
Last updated: 2026-04-07 (PLAN-912, Cesivi v251.0)