Skip to content

Planelets: Instant Plug-n-Play Integrations#5094

Open
ThatXliner wants to merge 25 commits into
superplanehq:mainfrom
ThatXliner:main
Open

Planelets: Instant Plug-n-Play Integrations#5094
ThatXliner wants to merge 25 commits into
superplanehq:mainfrom
ThatXliner:main

Conversation

@ThatXliner
Copy link
Copy Markdown

@ThatXliner ThatXliner commented May 30, 2026

For the 2026 SuperPlane Hackathon: Team EZDev

Summary

This PR introduces Planelets, a plugin system that lets anyone extend SuperPlane with custom integrations. A Planelet is a standalone HTTP server, built with a TypeScript SDK (or in any of the other languages we implemented), that exposes a manifest describing its actions, triggers, and configuration fields.

SuperPlane discovers these capabilities at runtime and renders them natively in the workflow canvas, meaning plugin-provided components look and behave identically to built-in ones. Instantly.

What's included

Backend integration (~1,600 lines of Go in pkg/integrations/planelet/):

  • Manifest-based capability discovery with a global cache and warm-on-startup preloading
  • RunAction executor that resolves dynamic configuration schemas from the manifest and dispatches to the plugin server
  • OnEvent trigger supporting webhook-style event ingestion from plugin servers
  • HTTP client with auth token support for plugin server communication
  • Full test coverage in planelet_test.go

DevEx and tooling:
Feel free to modify/restore/undo these changes. It was just necessary for me to use on my own local setup

  • Docker Compose compatibility with Podman (nested containers, fuse-overlayfs, iptables-legacy)
  • Dev entrypoint hardened against zombie air/Vite processes on container reuse
  • .dockerignore to keep build context lean

Design decisions worth noting

  1. The plugin server owns its own schema. Rather than requiring users to define configuration fields in SuperPlane's database, each Planelet manifest declares its own typed fields (text, number, boolean, select, list, object), and the workflow editor renders them dynamically.
  2. The manifest cache is intentionally simple, using a sync.Map keyed on server URL with no TTL. For the current use case (a handful of plugin servers per organization), this is appropriate, and it avoids the complexity of cache invalidation for something that changes infrequently. A refresh RPC exists on a separate branch for cases where users want to force-reload after a deploy.

How to test

  1. make dev.up && make dev.setup && make dev.server
  2. Add a Planelet integration pointing at a plugin server URL (try https://planelet.vercel.app/weather)
  3. The action picker should display the plugin's manifest-declared actions
  4. Configure and run a workflow node backed by the plugin

TODO post-Hackathon

  • Loading UI for fetching manifests
  • Better caching

ThatXliner and others added 23 commits May 30, 2026 08:29
PODMAN-SPECIFIC FIX. The dev compose stack failed to start under
Podman: `links` is unsupported ("link is not supported"), and the
`/tmp:/tmp` bind-mount inherited Podman rootless UID mapping so
container root could not write to /tmp, breaking `go build`.

Drop the deprecated/redundant `links` (compose DNS already resolves
services by name), drop the /tmp bind-mount (container gets its own
writable /tmp), and add `init: true` so tini reaps orphaned watcher
processes. All harmless under Docker.
PODMAN-SPECIFIC FIX. `make dev.server` launches the entrypoint via
`compose exec -d`. Under Podman, when the detached exec session
leader exits, the whole exec process group is SIGKILLed — so air,
vite, and the Go server they spawn die the moment the script returns,
leaving the app unreachable (502 from the Go proxy to a dead Vite).

Run each watcher under `setsid` so it lives in its own session and
survives the exec teardown. Redirect output to /app/tmp logs since
the detached session has no terminal. Harmless under Docker.
The SDK example installs a copy of the plugin-sdk into
sdk/example/node_modules, which itself contains example/node_modules
— an infinite recursive directory loop. Air walked it forever and
never built the server. The existing `node_modules` exclude only
matches the top level, not this nested path; exclude `sdk` outright
since it is TypeScript, not Go.
The plugin concept is branded "Planelets" in the canvas UI. Align the
backend so identifiers match the product name: package, directory,
registry key, integration type name, and the node IDs/event types
emitted on the wire (plugin.onEvent -> planelet.onEvent, etc.).

Safe to change the wire identifiers now since nothing uses the
integration yet — no existing canvases reference the old IDs.
Match the "Planelets" product branding used in the canvas UI and the
renamed backend integration. Renames the npm package
(@superplane/plugin-sdk -> @superplane/planelet-sdk), the public API
(createPlugin -> createPlanelet, PluginServer -> PlaneletServer,
PluginOptions -> PlaneletOptions), and the example that consumes it.
Follow the package and integration rename so the docs reference the
current @superplane/planelet-sdk API and "Planelets" canvas labels.
The example installs the SDK via `file:../`, which recreates the
parent (including example/node_modules) inside the install tree —
an infinite nesting that must never be committed.
@superplanehq-integration
Copy link
Copy Markdown

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

@ThatXliner
Copy link
Copy Markdown
Author

Note that docker-compose.dev.yml contain changes because Podman otherwise wouldn't have worked

@ThatXliner
Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants