SDK 1.0.0

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
Plugin pages run in a separate window; they don't enter the editor timeline and don't provide custom filters/transitions/encoders. Media probing, audio extraction, batch splitting and export are all done by the app — plugins must not connect to the editing engine themselves.

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))
Don't bundle the SDK, and don't write <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.

FieldRequirement
idRequired. Reverse-domain, globally unique; install folder name matches it
name / version / authorRequired. version is semver
mainEntry HTML, defaults to index.html
sdkVersionTarget SDK, currently ^1.0.0
permissionsSee section 4, shown to the user at install
slotsHome entries, id fixed to home.shortcut, optional relative icon
windowOptional. Default 800×600, resizable, min 480×360
mcpOptional. 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.

PermissionRiskEnables
draft.readLowlist, readBlob, draft.changed
draft.writeMediumcreate, writeBlob, remove, rename, stageMedia
fs.read / fs.writeHighFile read/write
net.fetch / net.localHighInternet / loopback requests
media.probeLowprobe, extractAudio, getEncodeCaps
media.renderHighBatch split startSplitJob / cancelSplitJob
export.renderHighenqueue, cancel, getStatus and export events
process.spawnHighLaunch external processes
ui.notify / ui.pickFiles / ui.pickDirectoryLowNotify, 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
Also available: 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

CodeMeaning
PERMISSION_DENIEDPermission not declared or not granted
UNKNOWN_METHODMethod or event name doesn't exist
NOT_FOUNDDraft, file or process handle not found
DRAFT_BUSY / EXPORT_BUSYDraft open in editor / editor open, can't enqueue
CONFLICTetag conflict, or target file exists
INVALID_DRAFTJSON unparseable or invalid format
PAYLOAD_TOO_LARGEExceeds per-call size limit
ENGINE_UNAVAILABLEEditing engine not ready
TIMEOUTExceeds the default 60s SDK timeout
INTERNALInternal 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.

This is a quick reference. The full SDK (all fs/net/process/media/mcp signatures and constraints) ships with the app. Download PixiuCut and press F12 in the plugin window to debug.