Packaging and testing
Build the quick-start plugin first. A distribution package contains your assembly, a manifest, and the files it actually needs at runtime. It does not include a private copy of the host SDK.
Manifest and compatibility
Use a version 2 plugin.json alongside the entry DLL. The quick-start manifest
shows the required fields; this example adds compatibility and dependency metadata:
{
"manifestVersion": 2,
"id": "com.example.myplugin",
"name": "My Plugin",
"version": "1.0.0",
"entryPoint": "MyPlugin.dll",
"description": "Adds a text prefix node and an example page.",
"authors": ["Example author"],
"sdkVersion": "1.15.0",
"platforms": ["win-x64"],
"tags": ["flow"]
}
Replace the example identity and description with your own. id uses lowercase
letters and digits separated by dots, underscores, or hyphens. entryPoint names
the assembly inside the package. Keep id and version consistent with the
entry class, and use semantic versions.
minAppVersionis the oldest app version you have verified. Set it when your plugin needs APIs or behavior absent from older apps.sdkVersionrecords the SDK you compiled against. The host rejects a newer SDK major version; it does not enforce availability of every minor-version API.platformslists supported OS/architecture identifiers.win-x64,linux-x64, andosx-arm64are examples. An omitted or empty list removes the platform gate; it does not make native code portable.dependenciesaccepts objects withid, optionalrange, and optionaloptional. These are plugin-package requirements, distinct from NuGet assemblies bundled with your plugin.
The SDK source includes plugin.schema.json for editor validation. Runtime
validation is not a guarantee of complete API compatibility. Test supported app
versions, including the oldest one you declare.
The current host does not automatically install manifest dependencies or enforce
their version ranges during plugin initialization. Implement
IPluginWithDependencies for runtime ID checks and ordering, and document which
other packages users must install. See dependencies.
Package layout
Use a dedicated folder so your dependencies do not mix with other plugins:
MyPlugin/
plugin.json
MyPlugin.dll
MyPlugin.deps.json
MyPlugin.pdb
Dependency.dll
runtimes/
win-x64/
native/
NativeDependency.dll
Only include files that apply to your plugin. PDB files are optional debug symbols. Include required managed dependencies and native libraries with their expected paths; a build that succeeds on your machine can still fail on a clean machine missing those files.
The host shares MultiShock.PluginSdk.dll and framework assemblies from its own
load context. Do not bundle the SDK, host executable, or an entire .NET runtime.
Use compatible framework package references instead of attempting to override
the host's System.* or Microsoft.* assemblies.
For private managed dependencies that are not copied by default, build with
CopyLocalLockFileAssemblies enabled and review the resulting files:
dotnet build .\MyPlugin.csproj -c Release -p:CopyLocalLockFileAssemblies=true
Copy only your runtime payload into a clean staging directory. For the basic quick-start plugin with no private dependencies:
New-Item -ItemType Directory -Path .\package
Copy-Item -LiteralPath .\bin\Release\net10.0\MyPlugin.dll -Destination .\package
Copy-Item -LiteralPath .\plugin.json -Destination .\package
Use a new or empty staging directory for every release. Add your actual private dependencies and metadata files before packaging; these two copies are sufficient only for the basic SDK-only example.
Install locally
- Save work, stop automation, and close MultiShock.
- Open
%LocalAppData%\PiShock\MultiShock\Plugins\on Windows. - Copy the staged directory there as
Plugins\MyPlugin\, withplugin.jsondirectly inside it. - Remove or move aside older loose DLLs or packages with the same plugin ID. Keep only one installed copy.
- Start MultiShock. Check Manage plugins and Debug & diagnostics → Plugins for the loaded version and any errors.
- Turn your page to Shown under Configure → Sidebar pages.
Loose DLL loading is also supported. Install from file copies the selected DLL and matching metadata files, not every private dependency or a directory's manifest. Use a manifest folder or package for a multi-file plugin.
The host loads shadow copies to reduce source-file locking. Those copies are
temporary implementation data, not your install location or settings directory.
Always update the original installed source and store settings under
GetPluginDataPath.
Development reload
For a local DLL or manifest-directory installation:
- Create an empty
MyPlugin.dll.devnext to the entry DLL, or an empty.devfile in that plugin's directory. - Restart MultiShock once. Confirm the plugin shows Dev mode.
- Rebuild and copy the changed payload to the same installed location.
- Open Manage plugins → Developer and click Reload for the plugin.
The Developer tab also provides controls for development plugins. Reload replaces plugin pages and services; it does not update the flow runtime's existing node type registrations. Restart the app after flow-node changes and when checking startup compatibility or changed manifests.
Use PluginServiceLocator for plugin-specific services in reloadable pages so
they resolve from the current plugin provider. If you own listeners, sockets,
hotkeys, or background tasks, implement reload cleanup as described in
lifecycle. A .dev marker enables a loader
mode; it does not clean up your resources for you.
Do not distribute .dev markers with release packages. The host can also remember
development mode in plugin configuration, so verify the mode when testing a
release in an existing profile.
Create a distributable archive
A .msplugin file is a ZIP archive with plugin.json and the entry DLL at its
root. Archive the contents of the staging folder, not a parent folder:
Compress-Archive -Path .\package\* -DestinationPath .\MyPlugin.zip
Move-Item -LiteralPath .\MyPlugin.zip -Destination .\MyPlugin.msplugin
Choose unused output names or move previous build artifacts aside first. Inspect the archive before distributing it. Users can copy it into their plugin folder and restart; see installing plugins. Keep plugin-owned settings and credentials out of the archive.
For catalog contributions, use the plugins repository and its current contribution/build workflow. Do not assume publishing a DLL or a NuGet package makes it appear in Browse. Catalog downloads carry a SHA-256 checksum that the host verifies before installation.
Verify before release
- Test a fresh app launch with the actual staged package and required dependencies. Your development output directory is not a substitute for the archive.
- Check the declared plugin ID/version, app and SDK compatibility, native platform, sidebar pages, configuration save/load after restart, and missing optional integrations.
- Exercise node defaults, invalid inputs, trigger start/stop, and two simultaneous instances using the node guide.
- Test disconnect and initialization failure paths, then inspect logs for useful errors and accidental secrets.
- Test reload only if you support it, and confirm listeners or hotkeys are not duplicated. Test normal startup again without a development marker.
- Keep device-output testing deliberate: choose an explicit target and vibration, verify saved limits, and keep Stop everything reachable.
Diagnose loading and reload failures
| Symptom | Check |
|---|---|
| Plugin never appears | Correct data folder, manifest, entry filename, public parameterless entry class, private dependencies, and diagnostics errors |
| Compatibility rejection | minAppVersion, SDK major version, and platforms; then actual API compatibility |
| Installed twice | An old DLL, extracted directory, or archive with the same plugin ID |
| Page missing | Full normalized @page route, navigation Href, and Sidebar pages visibility |
| Service missing after reload | Current provider through PluginServiceLocator, service registration, and initialization errors |
| Native load failure | OS/architecture, native payload, transitive native prerequisites, and runtimes/<rid>/native/ paths |
| Old flow-node behavior | Restart the app after node changes |
| File copy denied | Close the app and other processes holding the source files; copy to the original install directory |
Collect Recent Plugin Errors, the app and plugin versions, and the main/plugin logs from the failing run. See troubleshooting.