Appearance
Platform & hosting model
Plugin authors do not need to operate UnCorded infrastructure, but they do need the right boundaries. Most portability bugs come from collapsing hosting, reachability, and visibility into one idea called “the server.” They are independent.
The request path
text
client shell
│ authenticated HTTP / WebSocket
▼
server runtime ── validates manifest capabilities and user sessions
│
├── plugin backend (sandboxed Bun subprocess, stdio JSON IPC)
├── plugin data (private SQLite / KV / files)
├── plugin UI (HTML served to a sandboxed client iframe)
└── host brokers (fetch, proxy, Transport ports, voice, companions)The server runtime is the stable boundary. A plugin talks to it through the SDK, not to Docker, WSL, systemd, Central, or the desktop process.
Hosting backend
Packaged Windows builds default to native hosting: the desktop provisions an app-owned WSL2 distro, and the supervisor launches each server runtime as an isolated systemd unit with per-server ports, filesystem views, resource limits, and lifecycle control. Docker remains supported for fallback and migration.
Both hosts expose the same runtime roots to plugin machinery and the same SDK contract. Therefore:
- Do not invoke
docker,wsl, orsystemctlfrom a plugin. - Do not construct
/data, Windows, distro, or Docker volume paths yourself. Useplugin.db,plugin.kv,plugin.files, and manifest settings. - Do not assume
localhostorhost.docker.internalmeans the owner’s desktop. It depends on the hosting backend and network namespace. - Restart/install through the UnCorded desktop/plugin-management flow. The host owns credentials, mount setup, reconciliation, and process recreation.
Reachability modes
Reachability answers “how can a client get to this runtime?” It is not the same as hosting.
| Mode | HTTP/WebSocket shell path | Raw public ports | Dedicated proxy hostnames |
|---|---|---|---|
| Local-only | Same-device loopback | unavailable | unavailable |
| UnCorded Transport | Platform-managed server hostname/tunnel | plugin.net uses direct-first, relay fallback | available for approved mounts |
| Owner Cloudflare tunnel | Owner-managed public hostname | unavailable unless Transport is also configured | Transport-only |
Transport activation is entitlement- and grant-gated. A configured relay that loses its grant degrades closed; plugins should treat public reach as a runtime capability that may be temporarily unavailable, not a permanent property.
plugin.net is for public game/service ports
With net.public_ports, a backend may request a stable TCP or UDP address:
ts
const address = await plugin.net.requestPublicPort({
name: "game",
localPort: 25565,
protocol: "tcp",
});
// { host, port, proxyName }The runtime owns allocation and routing. The request is idempotent per (plugin, name), and releasePublicPort(name) is a no-op when nothing is allocated. On a local-only/non-Transport server the call fails with a typed availability error; expose that state to the owner instead of inventing a network fallback.
Visibility is authorization policy
visibility: "public" | "private" controls directory and membership behavior. It does not select native vs Docker, start a tunnel, or grant Transport. A private server can be publicly reachable but require membership. A local-only server can still have either visibility value in its record while remaining unreachable off-device.
Never use reachability or a public URL as authorization. Backend handlers must authorize the authenticated user supplied by the runtime.
Reaching an upstream service
Reverse-proxy plugins and homelab adapters must ask the owner for a URL that the runtime host can reach.
| Upstream location | Portable guidance |
|---|---|
| Another LAN device | Use its stable LAN DNS name or address; bind the service on an interface reachable from the runtime. |
| Public service | Use its HTTPS hostname and declare the exact http.fetch:<host> capability when calling plugin.fetch. |
| Windows host, native WSL runtime | Loopback forwarding may make 127.0.0.1:<port> reachable when the service is listening appropriately; verify on the target machine. |
| Windows host, Docker runtime | host.docker.internal:<port> is Docker-specific and the service normally must bind beyond loopback. |
| Managed workload shipped with a plugin | Use the companion declaration/broker when that manifest feature is released; do not guess host/port. |
There is no single magic hostname portable across native and Docker hosting. For marketplace-quality plugins, make the upstream a setting, label the host assumption, validate it with a health check, surface PROXY_UPSTREAM_ERROR clearly, and document both host paths.
Central and content ownership
Central issues identity/signing material, tracks server records and membership, and grants entitled Transport resources. User content remains on the self-hosted runtime. Plugins should use runtime APIs for users, permissions, data, and events; they should not call Central directly or expect Central to store plugin state.
Source map
The implementation behind this page lives in:
- native host and per-server isolation:
supervisor/src/ - desktop host abstraction:
apps/desktop/src/hosting-backend.ts - server provisioning modes:
apps/desktop/src/provision.ts - Transport controller and public ports:
runtime/src/transport/ - relay service:
relay/ - visibility normalization and live updates:
runtime/src/main.ts
For grep-ready links and invariants, see the AI agent guide.