Plugin Development Guide
For third-party developers building draft tools for PixiuCut. Implement against the method names, fields and error codes here — no need to read the app source.
1. What plugins can do
A plugin is a draft transformer: it reads a user draft and writes one or more new drafts. To produce a video, it can delegate to the app to send a saved draft into the task center for export.
input = a draft (plus the user's own media files)
output = new draft(s)
option = delegate the app to export drafts to a chosen directory
Good use cases:
- Batch-replace media in a template draft to generate variants, then enqueue export
- Read video info or supply your own audio, call your own ASR service, write back a subtitled draft
- Convert another app's project description into a PixiuCut draft
- Sync drafts to your own storage
2. Five-minute example
A minimal plugin needs only two files:
hello-plugin/
├── manifest.json
└── index.html
manifest.json
{
"id": "com.example.hello",
"name": "Hello Plugin",
"version": "1.0.0",
"description": "My first plugin",
"author": "Your Name",
"main": "index.html",
"sdkVersion": "^1.0.0",
"permissions": ["draft.read", "draft.write", "ui.notify"],
"slots": [{ "id": "home.shortcut", "label": "Hello" }],
"window": { "width": 640, "height": 480 }
}
Use the global object PixiuCutPlugin
const sdk = PixiuCutPlugin
async function init() {
const drafts = await sdk.draft.list()
console.log(`Connected, ${drafts.length} draft(s)`)
await sdk.ready()
}
async function makeVariant() {
const drafts = await sdk.draft.list()
if (!drafts.length) return sdk.ui.notify('Create a draft first', 'warning')
const { blob } = await sdk.draft.readBlob(drafts[0].id)
await sdk.draft.create(drafts[0].name + ' copy', blob)
await sdk.ui.notify('Copy created', 'success')
}
init().catch((e) => console.error(e))
<script src="sdk.js">. The app's local page server injects /sdk.js when returning your HTML. Pages run in a sandboxed iframe (sandbox="allow-scripts allow-forms allow-modals", without allow-same-origin), so localStorage is unavailable — write config to fs.dataDir().3. Package & manifest
No required build tool. Plain HTML, or a single entry page built with React / Vue / Svelte. Mark PixiuCutPlugin as an external global; don't bundle it. Static assets use paths relative to the plugin root.
| Field | Requirement |
|---|---|
id | Required. Reverse-domain, globally unique; install folder name matches it |
name / version / author | Required. version is semver |
main | Entry HTML, defaults to index.html |
sdkVersion | Target SDK, currently ^1.0.0 |
permissions | See section 4, shown to the user at install |
slots | Home entries, id fixed to home.shortcut, optional relative icon |
window | Optional. Default 800×600, resizable, min 480×360 |
mcp | Optional. Expose tools to the on-device assistant |
4. Permissions
Users see the permissions at install; high-risk ones are highlighted. Declare only what you need.
| Permission | Risk | Enables |
|---|---|---|
draft.read | Low | list, readBlob, draft.changed |
draft.write | Medium | create, writeBlob, remove, rename, stageMedia |
fs.read / fs.write | High | File read/write |
net.fetch / net.local | High | Internet / loopback requests |
media.probe | Low | probe, extractAudio, getEncodeCaps |
media.render | High | Batch split startSplitJob / cancelSplitJob |
export.render | High | enqueue, cancel, getStatus and export events |
process.spawn | High | Launch external processes |
ui.notify / ui.pickFiles / ui.pickDirectory | Low | Notify, pick files/dirs |
These are open by default without declaration: plugin.ready/resize/close/log/packageDir/setBusy, ui.getAppInfo, license.isActive/trialDaysLeft, events.subscribe, and fs.dataDir with its read/write.
5. SDK API
Global object window.PixiuCutPlugin; async methods return Promises and throw PluginError (err.code) on failure. Default per-call timeout is 60 seconds.
Drafts
const drafts = await PixiuCutPlugin.draft.list()
// { id, name, dir, created, modified, durationUs?, thumbnail, type }
const { blob, version } = await PixiuCutPlugin.draft.readBlob(draftId)
const summary = await PixiuCutPlugin.draft.create(name, blob?) // blob acts as a template
const res = await PixiuCutPlugin.draft.writeBlob(id, blob, version) // version for conflict check
await PixiuCutPlugin.draft.remove(id)
await PixiuCutPlugin.draft.rename(id, newName)
const { path } = await PixiuCutPlugin.draft.stageMedia(draftId, srcPath)
Media probe
const info = await PixiuCutPlugin.media.probe(filePath)
// { success, type, width, height, durationUs, frameRate, codec }
const audio = await PixiuCutPlugin.media.extractAudio(filePath, { format: 'wav' })
Delegated export
const { queueId } = await PixiuCutPlugin.export.enqueue(
[{ draftId: 'proj_…', outputDir: 'D:/exports', fileName: 'My title' }],
{ config: { format: 'mp4', vcodec: 'libx264', resolution: 1080, frameRate: 30 } },
)
PixiuCutPlugin.on('export.progress', ({ draftId, percent }) => {})
PixiuCutPlugin.on('export.queueIdle', ({ queueId }) => {})
UI & lifecycle
await PixiuCutPlugin.ui.notify('Done', 'success')
const files = await PixiuCutPlugin.ui.pickFiles({ multiple: true })
const dir = await PixiuCutPlugin.ui.pickDirectory()
await PixiuCutPlugin.ready()
await PixiuCutPlugin.setBusy(true) // during long tasks
const root = await PixiuCutPlugin.packageDir()
const data = await PixiuCutPlugin.fs.dataDir() // read/write without permission
fs.* file ops, net.fetch network proxy, process.* external processes, media.startSplitJob batch splitting, and mcp.* to register tools with the on-device assistant. Full signatures ship with the app's bundled guide.6. Draft format
A draft blob is a JSON string with format_version fixed to "1.2". Time fields are in microseconds (µs); project_info.created/modified/last_opened are seconds (Unix time).
{
"format_version": "1.2",
"project_info": { "id": "proj_…", "name": "My video", "created": 1700000000,
"modified": 1700000000, "duration": 0, "status": "editing" },
"project_settings": {
"video": { "width": 1080, "height": 1920, "frame_rate": 30 },
"audio": { "sample_rate": 48000, "channels": 2 }
},
"tracks": [
{ "id": "track_1", "type": "video", "name": "Main video",
"main_track": 1, "clips": [] }
]
}
clip.type is an integer: 1 video, 2 audio, 3 image, 4 text, 5 solid, 6 subtitle. Media clips must include name / type / start_time / in_point / out_point / media_duration / duration, where duration = (out_point - in_point) / speed_factor. A subtitle track holds one container clip (type:6, subtitle_container:true); each line is an entry in subtitle_track.items.
7. Error codes
| Code | Meaning |
|---|---|
PERMISSION_DENIED | Permission not declared or not granted |
UNKNOWN_METHOD | Method or event name doesn't exist |
NOT_FOUND | Draft, file or process handle not found |
DRAFT_BUSY / EXPORT_BUSY | Draft open in editor / editor open, can't enqueue |
CONFLICT | etag conflict, or target file exists |
INVALID_DRAFT | JSON unparseable or invalid format |
PAYLOAD_TOO_LARGE | Exceeds per-call size limit |
ENGINE_UNAVAILABLE | Editing engine not ready |
TIMEOUT | Exceeds the default 60s SDK timeout |
INTERNAL | Internal app error |
8. Publishing
Package as a single zip: after extraction, the root is manifest.json and the entry HTML — no extra wrapping folder, size under 80MB.
- Install from zip: Plugin manager → Installed → "Install from zip"
- Local folder: place under the app's plugin directory with the folder named after
manifest.id, then "Refresh" - Discovery catalog: provide a record (id, name, latestVersion, downloadUrl, …) to the official catalog maintainer; users install from "Plugins → Discover"
Enabled by default after install, appears as a home shortcut, no restart needed. On update the install directory is replaced while the fs.dataDir() data directory is preserved.
9. FAQ
Can I use React / Vue / Svelte?
Yes. The build output is a single entry HTML. Don't bundle the SDK; mark PixiuCutPlugin as an external global.
Can I call Node.js?
No. System capabilities go through SDK methods. For ffmpeg or bundled tools, use process.spawn with an absolute path, or ship the binary inside the plugin package.
Can I connect to the editing engine directly?
No. Probing, audio extraction, splitting and export all go through the SDK, performed by the app.
Can plugins call each other?
No. The handoff is: plugin A creates a draft, then the user opens plugin B to read it.
fs/net/process/media/mcp signatures and constraints) ships with the app. Download PixiuCut and press F12 in the plugin window to debug.