Skip to content

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

  1. Overview
  2. CSP Format Specification
  3. Property Types
  4. Template Rendering
  5. Asset Serving
  6. Walkthrough: Create Your First CSP Package
  7. REST API Reference
  8. 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.jsonwebparts 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.jsonproperties 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 type field affects how properties are rendered in the auto-generated editor. When you provide a custom edit.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. <&lt;). 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 ..) return 400 Bad Request
  • Asset serving is only available for CSP solutions (PackageType == "CSP"); WSP solutions return 404
  • 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 (including webpart.json and manifest.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, wpContext is provided automatically. You can reference wpContext.id to 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
  1. Navigate to /_layouts/15/solutions.aspx in your Cesivi instance
  2. Click Upload Solution
  3. Select hello-world.csp
  4. 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

  1. Navigate to any wiki page (e.g. /_layouts/15/wikiedit.aspx?page=...)
  2. Click Add Web Part (the + button in the ribbon or page)
  3. Find Hello World in the Custom category
  4. Click to insert
  5. 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 manifest
  • Feature.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)