---
title: Marketplace Registry
description: Publish and install gadgets + blueprints from the ggui marketplace. Versioned, Ed25519-signed, content-addressable distribution for ggui artifacts.
---

The marketplace registry is where authors publish ggui artifacts and consumers install them. It's HTTP-only — distinct from the WebSocket protocol — and hosts immutable, signed, content-addressable bundles + manifests. The registry is for published, signed artifacts; to move your own deployment's cached blueprints between servers, use `ggui export-pool` and load the pool on the other server with `ggui serve --seed-pool <dir>`.

## Three nouns, three roles

Marketplace docs and registry wire shapes use exactly three terms. Do not say "plugin" / "library" / "package" — see the [glossary](/glossary/) for the canonical vocabulary.

| Noun          | Role                                      | Where it lives                                  |
| ------------- | ----------------------------------------- | ----------------------------------------------- |
| **gadget**    | ingredient — composed into a UI           | `clientCapabilities.gadgets` (client-side hook) |
| **tool**      | action — invoked by the agent             | `agentCapabilities.tools` (agent-side function) |
| **blueprint** | recipe — returned directly as a cached UI | TSX source + optional contract + variance hints |

A gadget is a `GadgetDescriptor` (see [Gadgets SDK](/sdk/gadgets/)) packaged for distribution. A blueprint is a `(source, contract?, fixtureProps?, variance?)` quad. Both ride `ArtifactManifest` — a discriminated union on `kind: "gadget" | "blueprint"`.

## Registry resolution

The hosted registry lives at `registry.ggui.ai`; self-hosted registries work identically (see [Self-Hosted Registry](/sdk/self-hosted-registry/)).

The CLI resolves the registry URL per verb (highest precedence first):

- **publish / search** — `--registry <url>` flag → `GGUI_REGISTRY` env → `ggui.json#registry`. Errors if none is set.
- **install** — `--registry <url>` flag → `ggui.json#registry` → `GGUI_REGISTRY` env → built-in default `https://registry.ggui.ai`.

Note the orders differ: with both the env var and `ggui.json#registry` set, publishes follow the env var while installs follow `ggui.json`.

## CLI surface — two sibling verbs

Two parallel namespaces, one per artifact kind. Each verb hard-enforces its kind from the manifest — running a gadget verb in a blueprint repo (or vice versa) emits a friendly redirect and exits non-zero.

| Verb                              | What it does                                                                                              |
| --------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `ggui gadget create <id>`         | Scaffold `ggui.gadget.json` + `src/index.ts` hook stub                                                    |
| `ggui gadget publish`             | Bundle (esbuild), sign, upload from CWD                                                                   |
| `ggui gadget search [query]`         | List gadgets (`--kind=gadget` hard-locked)                                                                |
| `ggui gadget install <id>@<v>`    | Verify + register into `ggui.json#app.gadgets[]`                                                          |
| `ggui blueprint create <id>`      | Scaffold `ggui.blueprint.json` + `src/blueprint.tsx` + optional contract stub                             |
| `ggui blueprint publish`          | Sign + upload from CWD (no bundling — TSX rides inline on the manifest)                                   |
| `ggui blueprint search [query]`      | List blueprints (`--kind=blueprint` hard-locked)                                                          |
| `ggui blueprint install <id>@<v>` | Materialize to `.ggui/installed-blueprints/<scope>__<name>__<version>/` for `discoverLocalUis` to pick up |
| `ggui blueprint uninstall <id>@<v>` | Remove a locally-installed blueprint (deletes the materialized directory; when it was the last installed blueprint, also strips the install-managed glob from `ggui.json#blueprints.include`) |

The two namespaces are deliberately separate sibling verbs — NOT an umbrella `ggui artifact` that auto-detects kind. Gadgets and blueprints share a registry but mean different things to the install flow, so the kind discriminator is surfaced at the verb level.

## Author workflow

```bash
# === Gadget ===

# 1. Scaffold
ggui gadget create @my-org/weather-card
cd weather-card

# 2. Implement the hook + edit ggui.gadget.json (description, usage,
#    example, gotchas — these are required and shape the LLM's prompt
#    at code-gen time, so write them carefully).

# 3. Authenticate (the publish flow reuses the session `ggui login`
#    stores at ~/.ggui/auth.json, refreshing it automatically).
ggui login

# 4. Dry-run validates bundle + conformance preflight without
#    uploading. Recommended on every change.
ggui gadget publish --dry-run

# 5. Upload. First publish auto-generates an Ed25519 keypair and
#    registers your public key with the registry.
ggui gadget publish

# === Blueprint ===

# 1. Scaffold (different template — TSX source + contract stub)
ggui blueprint create @my-org/login-form
cd login-form

# 2. Edit src/blueprint.tsx + the contract; manifest carries `variance`
#    hints that prime the matcher.

# 3. Same auth setup as gadgets.

# 4. Publish — no bundling step; TSX rides inline on the manifest.
ggui blueprint publish
```

The publish CLIs run a shared internal pipeline that asserts `manifest.kind === verb-kind`, runs the registry's conformance gate, signs (gadget: `sha384(bundleBytes)` of the compiled bundle; blueprint: canonical JSON of the manifest), and POSTs `{manifest, bundle?, signature}` to `/publish`.

## Consumer workflow

```bash
# Search the registry — each namespace hard-locks its kind filter.
ggui gadget search "weather"
ggui gadget search --hook=useWeatherCard
ggui blueprint search "login"

# Install. Verifies signature + SRI before writing anything.
ggui gadget install @my-org/weather-card@0.1.0      # → ggui.json#app.gadgets
ggui blueprint install @my-org/login-form@0.1.0     # → .ggui/installed-blueprints/
```

Install always runs both verification legs (bundleSha384 recompute + Ed25519). If the registry didn't pin a public key on the version (legacy / pre-key-pinning artifacts), the default behavior is **warn-and-continue**; pass `--strict` to turn that into a hard exit 1 instead.

## Auth

`publish` (always) and private-row reads accept `--auth=bearer --token=<value>` (or `GGUI_REGISTRY_TOKEN`) for self-hosted / local-development flows. Without the flag, the CLI sends the access token from your stored [`ggui login`](/cli/login/) session (`~/.ggui/auth.json`), refreshing it automatically when expired. The hosted registry's authenticated endpoints (`/publish`, `/conformance/check`, `/author-keys`) accept exactly this credential — run `ggui login` once and the default auth path works end-to-end. Third parties running their own [`@ggui-ai/registry-server`](/sdk/self-hosted-registry/) deployments pass `--auth=bearer`.

## Trust model

The signing posture is determined by `manifest.visibility`. Both posters share the same `bundleSha384`-recompute fast leg; they differ only on the cryptographic verify leg.

| `visibility` | Algorithm       | Trust root                                  | Key/identity binding                                                   |
| ------------ | --------------- | ------------------------------------------- | ---------------------------------------------------------------------- |
| `public`     | sigstore-cosign | Fulcio cert + Rekor inclusion proof         | OIDC subject embedded in the Fulcio leaf cert                          |
| `private`    | Ed25519         | AuthorKeys table (per-publisher pinned key) | `publicKeyId` = `base64(sha256(publicKey))[:16]` (16-char fingerprint) |

**Per-author Ed25519 keys (private gadgets).** On first publish, the CLI generates a 32-byte private key at `~/.ggui/keys/<scope>/private.key`. The public key (`base64(pk)`) is registered with the registry under the publisher's server-side account subject — see [`ggui keys register`](/cli/keys-register/) for the explicit verb and the registration error matrix. The registry copies the publishing key onto every `ArtifactVersionRow` it writes (field: `authorPublicKey`, base64-encoded raw Ed25519 public key bytes). A later key rotation does NOT invalidate historical versions — each version verifies against the exact key that signed it.

**Sigstore (public gadgets).** Public artifacts sign via the keyless sigstore flow — the CLI obtains a short-lived Fulcio certificate bound to your OIDC identity and submits the signature to Rekor for a public transparency-log entry. There is no long-lived author key for the public path; trust roots in Fulcio + Rekor.

**Two-leg verification on install.** Both must pass before any disk write:

1. **bundleSha384 recompute.** Compute `sha384(payload)`, compare against `signature.bundleSha384`. Fast tamper detection. Runs for both algorithms.
2. **Cryptographic verify.** Dispatches on `signature.algorithm`:
   - `ed25519` → `derivePublicKeyId(returnedPubkey) === signature.publicKeyId` (rejects key-swap attacks), then `verifyBundleEd25519`. If the registry didn't pin a public key for the version, the CLI warns and continues by default; `--strict` turns the warn into a hard fail.
   - `sigstore-cosign` → `verifyBundleSigstore` validates the Fulcio cert chain + Rekor inclusion. By default any valid OIDC identity is accepted; pass `--verify-identity <subject-or-/regex/>` to additionally pin the Fulcio leaf's subject claim.

## Subresource integrity (`bundleSri`)

Publish computes `sha384-<base64(sha384(bundle))>` server-side. The read endpoint returns it; install writes it onto `GadgetDescriptor.bundleSri`; the iframe runtime emits a `<link rel="modulepreload" integrity="...">` tag before dynamic-importing the bundle. A CDN compromise can't silently swap the bundle without breaking SRI.

CSP `script-src` auto-derives from each entry's `bundleUrl` origin — no per-registry allowlist needed.

## Versioning

- **Semver.** `1.2.3-alpha.1+build.42` form. `parseArtifactManifest` rejects ranges, `latest`, leading zeros (applies to gadgets and blueprints alike).
- **Immutable.** `POST /publish` of an existing `(scope, name, version)` triple returns `409 version_exists`.
- **Yank, not delete.** Yanked versions return `410 Gone` with the manifest still in the body for audit. Hard delete is intentionally NOT supported.

## HTTP API surface (core routes)

The core routes (version-listing `GET /pkg/{scope}/{name}` and the `/bundles/*` fetch routes ride alongside):

| Method | Path                                    | Auth        | Purpose                                                               |
| ------ | --------------------------------------- | ----------- | --------------------------------------------------------------------- |
| `GET`  | `/search?q=&kind=&hook=&tag=&author=&…` | public      | List artifacts. AND-semantics across filters.                         |
| `GET`  | `/pkg/{scope}/{name}/{version}`         | mixed[^1]   | Read a single version's manifest + fetchable URLs + signing metadata. |
| `POST` | `/publish`                              | CLI session | Upload `{manifest, bundle?, bundleSha384?, signature}`.               |
| `POST` | `/conformance/check`                    | CLI session | Dry-run the conformance gate without publishing.                      |

[^1]: Public for `visibility: "public"` artifacts; an account credential is required for `visibility: "private"`. "CLI session" = the `ggui login` session token, verified at the hosted deployment's gateway (see [Auth](#auth)).

Wire shapes are defined in `@ggui-ai/registry-core` (shared by the hosted cloud and the self-hosted server).

## What's in `ggui.json` after install

**Gadgets** land on `app.gadgets[]`:

```json
{
  "app": {
    "gadgets": [
      {
        "package": "@my-org/weather-card",
        "version": "0.1.0",
        "bundleUrl": "https://registry.ggui.ai/bundles/...",
        "bundleSri": "sha384-Wj1...",
        "exports": [
          {
            "hook": "useWeatherCard",
            "description": "Renders a weather card.",
            "usage": "Use to surface current weather.",
            "example": { "city": "Berlin" }
          }
        ]
      }
    ]
  }
}
```

**Blueprints** materialize to disk under `.ggui/installed-blueprints/<scope>__<name>__<version>/`:

```
.ggui/installed-blueprints/
└── my-org__weather-card__0.1.0/
    ├── index.tsx         # the published TSX source
    └── ggui.ui.json      # generated UI manifest (id, contract, entryPoint)
```

The install adds `.ggui/installed-blueprints/**/ggui.ui.json` to `ggui.json#blueprints.include` automatically. The existing `discoverLocalUis` glob loader picks them up at boot — no special-case install path on the server side.

## Agent-facing MCP tools

The marketplace surface for the _agent_ (not the publisher CLI) lives on the same MCP endpoint as everything else. Three tools enumerate and materialize blueprints from the registry-backed catalog:

| Tool                            | Purpose                                                                            |
| ------------------------------- | ---------------------------------------------------------------------------------- |
| `ggui_list_featured_blueprints` | Enumerate builder-curated featured blueprints (no filters; takes empty input).                                                          |
| `ggui_search_blueprints`        | Semantic search across the app's blueprints (manifest + cached generations).                                                            |
| `ggui_render_blueprint`         | Resolve a registered blueprint by id to its compiled JS bundle — returns `{blueprintId, blueprintName, code, contentType}`; no session is created. |

Full wire shapes + agent-side usage: [MCP Protocol Reference](/api/mcp-protocol/#ggui_list_featured_blueprints). Gadgets are advertised via [`ggui_list_gadgets`](/api/mcp-protocol/#ggui_list_gadgets) instead — the catalog rides on the contract, not as a separate "render" call.

## Roadmap

Tracked-but-not-yet-shipped surfaces, in rough priority order:

- **Per-org private artifacts.** `visibility: "private"` today rejects only on missing JWT; per-org group enforcement on the read handler is the next step.
- **Yank UX.** No `ggui gadget yank` subcommand yet — yanking goes through registry admin tooling. The read + install sides already handle `410 Gone` correctly (install exits 1 with a "yanked" message).
- **Cross-registry federation / mirror.** Single-registry installs only; federated lookup is a separate slice.
- **Per-user authentication.** Author identity is the hosted account's subject today; team workflows share one account. Per-user identity within a team is future work.

## Reference

- **Protocol spec.** [Protocol overview](/protocol/overview/)
- **Self-hosting.** [Self-hosted registry](/sdk/self-hosted-registry/) — byte-compatible `@ggui-ai/registry-server` for air-gapped or local-dev use.
- **Glossary.** [gadget / tool / blueprint](/glossary/) — the three-noun vocabulary used throughout.
- **Author manifest.** [`@ggui-ai/artifact-manifest`](/sdk/gadgets/) — `gadgetManifestSchema` / `blueprintManifestSchema`.
- **Signing.** [`@ggui-ai/gadget-signing`](/sdk/gadgets/) — universal Ed25519 (browser + Node).
- **Publisher key registration.** [`ggui keys register`](/cli/keys-register/).