View source on GitHub
load-plugin. That one calls the API
with your key to start a conversation with a plugin loaded. Here we make a
no-code launch link anyone can click — perfect for an HTML <button> or a
Markdown badge in a plugin’s README.
Like load-plugin, this example is self-contained: it ships its own
dad-joke/ plugin and the launch links load it straight from
this repo on GitHub.
The link points at the OpenHands frontend /launch route:
plugins, shows a confirmation modal
(pre-filling any parameter fields), and on submit calls
POST /api/v1/app-conversations — the exact call load-plugin makes by
hand. The user supplies their own auth by being logged in, so the link
contains no secrets.
Official docs: Plugin Launcher
is the reference for the
/launch route — the plugins/message params,
how parameters become editable inputs, and a simpler unencoded format for
development. This example is a runnable companion to that page.Full end-to-end trace (marketplace → directory → frontend → app server → SDK):
Plugin Launch Flow design doc.Try It
These are the actual badges this example generates — click one to launch a conversation with the bundleddad-joke plugin:
- Tell a dad joke — runs
/dad-joke:aboutimmediately (variant 1). - Open with dad-joke loaded — loads the plugin and waits for your prompt (variant 2).
The badges fetch the plugin from this repo’s default branch, so they work
once this example is merged to
main. Testing from a branch? Regenerate them
with --ref your-branch (see below).Run It
Walkthrough: Encoding the Launch URL
The whole trick is turning a list of plugin specs into one URL-safe query parameter. Three steps (encode_plugins in build_launch_url.py):
build_launch_url):
- Default
json.dumpsseparators (", "/": ") are kept, matching the encoding the OpenHands plugin directory uses. - URL-escape the base64. Standard base64 can contain
+,/, and=, which are unsafe in a query string.quote(..., safe="")turns the=padding into%3D, etc. The frontend reverses this automatically. - It’s reversible —
decode_plugins()(base64-decode →json.loads) gets you back the original list. The script asserts this round-trip on every run.
The plugin spec fields
parameters are only defaults for the form. The user can edit them in the
modal before starting; the app server then formats the final values into the
conversation’s first message. (The SDK’s PluginSource itself has no
parameters field — see the design doc’s “Parameter Journey”.)
Simpler format for quick tests
For local or staging experiments you can skip base64 entirely and pass unencoded query params —plugin_source, plugin_ref, plugin_repo_path:
plugins form is what you want for shareable badges — and it’s the
only one that supports multiple plugins (or pre-filled parameters) in a single
link. Both formats are documented on the
Plugin Launcher
page.
Two Variants
1. Run a skill on launch — entry command
Setmessage to the plugin’s entry slash command. The conversation starts and
immediately runs the skill. dad-joke declares entry_command: "about", so
its command is /dad-joke:about, and the animal parameter pre-fills the modal:
2. Just load the plugin — user prompts after
Omitmessage. The conversation starts with the plugin’s skills loaded but
no first action, so the user types their own prompt. The dad-joke skill is
keyword-triggered: when the user asks for a joke, it asks for their favorite
animal and then delivers.
Use the Functions in Your Own Tooling
build_launch_url.py is importable:
Related
load-plugin
the programmatic equivalent (the API call this link ultimately triggers).
Plugin Launcher
official docs for the /launch route.
Plugin Marketplace
the plugin directory: a browseable catalog (served at /plugins, with a /api/plugins API) that builds launch links like these from a marketplace source repo. This example is what that directory does, by hand.
Plugins overview
what plugins are and the format they follow.
Plugin Launch Flow design doc
the full marketplace → directory → frontend → app server → SDK journey.

