# Install marketplace plugins

> Install plugins from the Paca-AI/paca-plugins catalog via the admin UI or API; artifact layout, migrations, and filesystem install scripts.

- Repository: Paca-AI/paca
- GitHub: https://github.com/Paca-AI/paca
- Human docs: https://grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25
- Complete Markdown: https://grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25/llms-full.txt

## Source Files

- `docs/plugins/marketplace.md`
- `docs/plugins/overview.md`
- `scripts/install-plugin.sh`
- `scripts/install-local-plugin.sh`
- `services/api/internal/transport/http/handler/plugin_handler.go`
- `deploy/.env.production.example`

---

---
title: "Install marketplace plugins"
description: "Install plugins from the Paca-AI/paca-plugins catalog via the admin UI or API; artifact layout, migrations, and filesystem install scripts."
---

Paca installs marketplace plugins by fetching `catalog/plugins.json` from the public [Paca-AI/paca-plugins](https://github.com/Paca-AI/paca-plugins) repository, downloading HTTPS artifact tarballs, writing them into configured local plugin directories, registering the plugin in PostgreSQL, running per-plugin SQL migrations, and loading the WASM runtime module. Admins trigger installs from **Admin → Plugin Settings → Marketplace** or from `POST /api/v1/admin/plugins/marketplace/install`.

## Prerequisites

| Requirement | Detail |
|---|---|
| Admin access | Global `users.write` permission (super-admin role or equivalent) |
| Authentication | Valid JWT session cookie or `X-API-Key` header |
| Writable plugin stores | API container must write to `PLUGINS_WASM_DIR`, `PLUGINS_FRONTEND_DIR`, and optionally `PLUGINS_MCP_DIR` |
| Store mode | Marketplace installer writes to the local filesystem; set `PLUGINS_STORE=local` |
| Outbound HTTPS | API must reach the catalog URL and all artifact URLs (HTTPS only; private IPs blocked) |
| Gateway | Nginx must serve frontend bundles at `/plugins/` and MCP bundles at `/plugins-mcp/` |

<Note>
Marketplace installation is separate from local plugin authoring. Use `scripts/install-local-plugin.sh` when you are building a plugin from source on disk, not when installing a catalog entry.
</Note>

## Catalog source

The marketplace catalog is a JSON document published in the `Paca-AI/paca-plugins` GitHub repository.

| Field | Value |
|---|---|
| Repository | `Paca-AI/paca-plugins` |
| Catalog path | `catalog/plugins.json` |
| Default URL | `https://raw.githubusercontent.com/Paca-AI/paca-plugins/master/catalog/plugins.json` |
| Publish model | Plugin developers open pull requests to add or update entries after publishing release artifacts |

### Catalog entry shape

Each plugin entry includes metadata and downloadable artifact URLs:

```json
{
  "schema_version": 1,
  "source": "https://github.com/Paca-AI/paca-plugins",
  "generated_at": "2026-05-08T00:00:00Z",
  "plugins": [
    {
      "name": "com.paca.checklist",
      "display_name": "Checklist",
      "description": "Adds named checklists with checkable items to tasks.",
      "version": "0.1.0",
      "avatar_url": "https://raw.githubusercontent.com/Paca-AI/paca-plugins/main/assets/checklist.png",
      "repository_url": "https://github.com/Paca-AI/paca-plugin-checklist",
      "artifacts": {
        "backend_tar_gz_url": "https://github.com/.../backend.tar.gz",
        "frontend_tar_gz_url": "https://github.com/.../frontend.tar.gz",
        "migrations_tar_gz_url": "https://github.com/.../migrations.tar.gz",
        "manifest_tar_gz_url": "https://github.com/.../manifest.tar.gz",
        "mcp_tar_gz_url": "https://github.com/.../mcp.tar.gz"
      }
    }
  ]
}
```

| Field | Required | Notes |
|---|---|---|
| `name` | Yes | Reverse-DNS identifier (e.g. `com.paca.checklist`) |
| `version` | Yes | Strict `X.Y.Z` semver for upgrade comparisons |
| `description` | Recommended | Shown in the admin marketplace panel |
| `display_name` | Optional | Human-readable label |
| `avatar_url` | Optional | Plugin icon in the UI |
| `repository_url` | Optional | Source link in the UI |
| `artifacts.manifest_tar_gz_url` | Yes | Must contain `plugin.json` |
| `artifacts.backend_tar_gz_url` | Optional | WASM binary tarball |
| `artifacts.frontend_tar_gz_url` | Optional | Module-federation frontend bundle |
| `artifacts.migrations_tar_gz_url` | Optional | SQL migration files |
| `artifacts.mcp_tar_gz_url` | Optional | MCP ESM bundle for `@paca-ai/paca-mcp` |

All artifact URLs must use HTTPS and resolve to public IP addresses. The API validates URLs at catalog fetch time to reduce SSRF risk.

## Install lifecycle

```mermaid
sequenceDiagram
    participant Admin as Admin UI / API client
    participant API as API service
    participant Catalog as paca-plugins catalog
    participant FS as Plugin filesystem stores
    participant DB as PostgreSQL
    participant RT as WASM runtime

    Admin->>API: POST /admin/plugins/marketplace/install
    API->>Catalog: GET catalog/plugins.json
    Catalog-->>API: Plugin entry + artifact URLs
    API->>Catalog: Download manifest/backend/frontend/migrations/mcp tar.gz
    API->>FS: Extract and copy artifacts
    API->>DB: INSERT plugins row
    API->>DB: Run plugin SQL migrations (per-plugin schema)
    API->>RT: Load WASM module
    alt Any step after artifact write fails
        API->>FS: Remove downloaded artifacts
        API->>DB: Delete plugin row (if created)
    end
    API-->>Admin: 201 Created + PluginResponse
```

On failure after artifacts are written but before the install completes, the handler removes downloaded files and deletes any partially created database record. Migration or runtime load failures roll back the DB registration.

## Install via admin UI

<Steps>
<Step title="Open Plugin Settings">

Log in as an admin user with `users.write` permission and navigate to **Admin → Plugin Settings**. The page is at `/admin/plugins` and defaults to the **Marketplace** tab.

</Step>
<Step title="Browse the catalog">

The panel calls `GET /api/v1/admin/plugins/marketplace` and renders each catalog entry with version, description, capability badges (Backend, Frontend, Migrations, MCP), and install state.

</Step>
<Step title="Install a plugin">

Click **Install** on the desired plugin card. The UI posts:

```json
{ "name": "com.paca.checklist", "enabled": true }
```

Installation is synchronous. The card shows **Installing...** until the API returns.

</Step>
<Step title="Verify the install">

Confirm the plugin card shows **Installed**. Check `GET /api/v1/plugins` for the new entry with `enabled: true`. If the plugin declares frontend extension points, reload the web app and confirm the plugin UI surfaces appear.

</Step>
</Steps>

Installed plugins with a newer catalog version show an **Update available** badge. Click **Upgrade to {version}** to call `POST /api/v1/admin/plugins/:pluginId/upgrade`.

## Install via API

All marketplace admin endpoints require authentication plus global `users.write` permission.

### List catalog entries

:::endpoint GET /api/v1/admin/plugins/marketplace
Fetch the validated marketplace catalog and return plugin entries for the admin UI or automation.
:::

<RequestExample>

```bash
curl -s http://localhost/api/v1/admin/plugins/marketplace \
  -H "X-API-Key: $API_KEY"
```

</RequestExample>

<ResponseExample>

```json
{
  "data": {
    "plugins": [
      {
        "name": "com.paca.checklist",
        "display_name": "Checklist",
        "description": "Adds named checklists with checkable items to tasks.",
        "version": "0.1.0",
        "avatar_url": "https://raw.githubusercontent.com/.../checklist.png",
        "repository_url": "https://github.com/Paca-AI/paca-plugin-checklist",
        "artifacts": {
          "backend_tar_gz_url": "https://github.com/.../backend.tar.gz",
          "frontend_tar_gz_url": "https://github.com/.../frontend.tar.gz",
          "migrations_tar_gz_url": "https://github.com/.../migrations.tar.gz",
          "manifest_tar_gz_url": "https://github.com/.../manifest.tar.gz"
        }
      }
    ]
  }
}
```

</ResponseExample>

### Install from marketplace

:::endpoint POST /api/v1/admin/plugins/marketplace/install
Resolve a catalog entry by reverse-DNS name, download artifacts, register the plugin, run migrations, and load the runtime.
:::

<ParamField body="name" type="string" required>
Reverse-DNS plugin identifier matching a catalog entry `name` field (e.g. `com.paca.checklist`).
</ParamField>

<ParamField body="enabled" type="boolean">
Whether the plugin is active immediately after install. Defaults to `true` when omitted.
</ParamField>

<RequestExample>

```bash
curl -s -X POST http://localhost/api/v1/admin/plugins/marketplace/install \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"com.paca.checklist","enabled":true}'
```

</RequestExample>

<ResponseExample>

```json
{
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "com.paca.checklist",
    "version": "0.1.0",
    "manifest": { "id": "com.paca.checklist", "displayName": "Checklist", "version": "0.1.0" },
    "enabled": true,
    "installed_at": "2026-06-13T12:00:00Z",
    "updated_at": "2026-06-13T12:00:00Z"
  }
}
```

</ResponseExample>

| HTTP status | Error code | Cause |
|---|---|---|
| 404 | `PLUGIN_NOT_FOUND` | Name not present in catalog |
| 400 | `BAD_REQUEST` | Artifact download, extraction, or manifest ID mismatch |
| 409 | `PLUGIN_NAME_TAKEN` | Plugin already installed (use upgrade instead) |
| 500 | `INTERNAL_ERROR` | Migration failure or WASM runtime load failure |
| 403 | — | Caller lacks `users.write` permission |

### Upgrade an installed plugin

:::endpoint POST /api/v1/admin/plugins/:pluginId/upgrade
Fetch the latest catalog version for the installed plugin name, verify semver ordering, replace artifacts, run new migrations, reload runtime, then update the database record.
:::

| HTTP status | Error code | Cause |
|---|---|---|
| 409 | `PLUGIN_ALREADY_UP_TO_DATE` | Catalog version equals installed version |
| 400 | `PLUGIN_DOWNGRADE_NOT_ALLOWED` | Catalog version is older than installed version |
| 400 | `BAD_REQUEST` | Downloaded manifest version does not match catalog version |

Upgrade only accepts strict `X.Y.Z` versions. Pre-release identifiers (`1.0.0-beta.1`) and build metadata (`1.0.0+001`) are rejected.

### Uninstall a plugin

:::endpoint DELETE /api/v1/admin/plugins/:pluginId
Delete the database record, unload the WASM module, and remove backend, frontend, and MCP artifacts from the filesystem.
:::

The admin UI **Uninstall** button calls this endpoint with the plugin UUID.

## Artifact layout

After a marketplace install, artifacts land in three filesystem stores configured by environment variables.

:::files
PLUGINS_WASM_DIR/                    # default: /plugins (prod) or ./plugins/local/backend (dev)
  <plugin-id>/
    backend.wasm                     # WASM binary from backend tarball
    plugin.json                      # manifest (id must match catalog name)
    migrations/
      001_init.sql                   # optional SQL files

PLUGINS_FRONTEND_DIR/                # default: /plugins-frontend (prod) or ./plugins/local/frontend (dev)
  <plugin-id>/
    assets/
      remoteEntry.js                 # module-federation entry
    ...                              # remaining built assets

PLUGINS_MCP_DIR/                     # default: /plugins-mcp (prod) or ./plugins/local/mcp (dev)
  <plugin-id>/
    mcp.js                           # ESM bundle for paca-mcp plugin loader
:::

### HTTP serving paths

| Store | Container path (prod) | Public URL |
|---|---|---|
| Frontend | `/plugins-frontend/<id>/` | `/plugins/<id>/assets/remoteEntry.js` via nginx |
| MCP | `/plugins-mcp/<id>/` | `/plugins-mcp/<id>/mcp.js` via nginx |
| Backend | `/plugins/<id>/` | Not served over HTTP (API-only) |

Set `remoteEntryUrl` in `plugin.json` to `/plugins/<plugin-id>/assets/remoteEntry.js`. Set MCP `remoteEntryUrl` to `${PUBLIC_URL}/plugins-mcp/<plugin-id>/mcp.js`.

<Warning>
The nginx gateway serves only frontend and MCP directories. WASM binaries and migration SQL files are mounted exclusively on the API container and are not reachable over HTTP.
</Warning>

### Download and extraction limits

| Constraint | Value |
|---|---|
| Max download size | 100 MB per artifact |
| Max file size in archive | 50 MB |
| Max files per archive | 10,000 |
| Archive format | `.tar.gz` |

The installer validates that `plugin.json` `id` matches the catalog `name` before writing files.

## Database migrations

Plugins with `migrations_tar_gz_url` artifacts receive a dedicated PostgreSQL schema namespace.

1. `CREATE SCHEMA IF NOT EXISTS "<plugin-name>"` (quoted identifier)
2. `CREATE TABLE IF NOT EXISTS plugin_kv` for plugin storage host functions
3. `CREATE TABLE IF NOT EXISTS plugin_schema_migrations` to track applied files
4. Apply pending `.sql` files in lexicographic filename order inside per-file transactions

Migrations are idempotent: already-applied filenames are skipped. The migration runner also runs on API startup for all enabled plugins.

<Info>
Plugin data lives in per-plugin schemas and is retained on uninstall unless the plugin implements explicit cleanup. Uninstall removes filesystem artifacts and the `plugins` registry row but does not drop plugin schemas.
</Info>

## Local filesystem install scripts

For plugin development (not marketplace catalog installs), use the repository install scripts to build artifacts locally and register them via the admin API.

<Tabs>
<Tab title="install-plugin.sh">

`scripts/install-plugin.sh` is a thin wrapper that delegates to `install-local-plugin.sh`:

```bash
./scripts/install-plugin.sh /path/to/paca-plugin-example --api-key $API_KEY
```

</Tab>
<Tab title="install-local-plugin.sh">

`scripts/install-local-plugin.sh` performs a full local build-and-register workflow:

1. Build `backend.wasm` with `GOOS=wasip1 GOARCH=wasm go build`
2. Build frontend with `bun run build`
3. Copy artifacts into `plugins/local/backend/<id>/` and `plugins/local/frontend/<id>/`
4. Register or update the plugin via the admin API

```bash
export API_KEY=your-api-key
./scripts/install-local-plugin.sh /path/to/plugin \
  --api-url http://localhost \
  --api-key $API_KEY
```

| Flag | Purpose |
|---|---|
| `--skip-build` | Copy existing build output only |
| `--skip-install` | Build artifacts without API registration |
| `--paca-dir` | Override Paca repo root (default: parent of `scripts/`) |
| `--api-url` | API base URL (default: `http://localhost`) |

</Tab>
<Tab title="remove-plugin.sh">

`scripts/remove-plugin.sh` removes a plugin by reverse-DNS ID:

```bash
./scripts/remove-plugin.sh com.paca.example --api-key $API_KEY
```

| Flag | Purpose |
|---|---|
| `--unregister-only` | Delete DB record; keep local artifact files |
| `--remove-artifacts-only` | Delete local files; keep DB record |

</Tab>
</Tabs>

### Manual registration after copying artifacts

If you populate the local stores manually, register the plugin with `POST /api/v1/admin/plugins`:

```bash
curl -X POST http://localhost/api/v1/admin/plugins \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"com.paca.example\",
    \"version\": \"0.1.0\",
    \"manifest\": $(cat /path/to/plugin.json),
    \"enabled\": true
  }"
```

<Tip>
After a filesystem-only install, restart Paca services or ensure the API reloads the plugin runtime so WASM modules pick up new binaries.
</Tip>

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `PLUGINS_MARKETPLACE_CATALOG_URL` | `https://raw.githubusercontent.com/Paca-AI/paca-plugins/master/catalog/plugins.json` | Catalog fetch URL |
| `PLUGINS_MARKETPLACE_TIMEOUT` | `20s` | HTTP timeout for catalog fetch and artifact downloads |
| `PLUGINS_STORE` | `local` | WASM load source (`local` or `s3`); marketplace installer requires writable local dirs |
| `PLUGINS_WASM_DIR` | `/plugins` (prod), `./plugins/local/backend` (dev) | Backend WASM, manifest, migrations |
| `PLUGINS_FRONTEND_DIR` | `/plugins-frontend` (prod), `./plugins/local/frontend` (dev) | Frontend bundles |
| `PLUGINS_MCP_DIR` | `/plugins-mcp` (prod), `./plugins/local/mcp` (dev) | MCP ESM bundles |
| `PLUGINS_S3_PREFIX` | `plugins` | S3 key prefix when `PLUGINS_STORE=s3` (runtime load only) |
| `PUBLIC_URL` | — | Base URL for MCP `remoteEntryUrl` resolution |

Override the catalog URL to point at a fork or pinned branch when testing custom marketplace entries:

```bash
PLUGINS_MARKETPLACE_CATALOG_URL=https://raw.githubusercontent.com/your-org/paca-plugins/main/catalog/plugins.json
```

## Troubleshooting

| Symptom | Likely cause | Resolution |
|---|---|---|
| Marketplace tab empty or loading forever | Catalog URL unreachable or invalid JSON | Verify `PLUGINS_MARKETPLACE_CATALOG_URL`; check API logs for fetch errors |
| `marketplace installer not configured` | Marketplace client or installer not wired | Ensure API started with plugin subsystem enabled (default in compose stacks) |
| `failed to install plugin artifacts` | Artifact URL not HTTPS, private IP, or download failure | Confirm release URLs are public HTTPS; check outbound network from API container |
| `manifest id does not match catalog name` | `plugin.json` `id` differs from catalog `name` | Fix the plugin manifest or catalog entry |
| `failed to run plugin migrations` | Invalid SQL or schema conflict | Inspect API logs for the failing filename; fix migration in plugin release |
| `failed to load plugin runtime` | Missing or corrupt `backend.wasm` | Verify backend artifact tarball; check `PLUGINS_WASM_DIR` permissions |
| Plugin UI does not appear | Frontend not served or `remoteEntryUrl` wrong | Confirm nginx `/plugins/` alias; verify `PLUGINS_FRONTEND_DIR` mount |
| MCP tools missing after install | No `mcp_tar_gz_url` or MCP bundle not served | Install MCP artifact; set `remoteEntryUrl` under `PUBLIC_URL/plugins-mcp/` |
| `PLUGIN_NAME_TAKEN` on install | Plugin already registered | Use **Upgrade** or uninstall first |
| 403 on admin endpoints | Missing `users.write` | Use a super-admin account or API key with admin permissions |

<Check>
Verify a successful marketplace install by confirming three signals: the plugin appears in `GET /api/v1/plugins` with `enabled: true`, artifact files exist under `PLUGINS_WASM_DIR/<name>/`, and the plugin's extension points render in the web UI (for frontend plugins).
</Check>

## Related pages

<CardGroup>
<Card title="Plugin system" href="/plugin-system">
WASM sandbox, extension points, capability permissions, and the full plugin lifecycle.
</Card>
<Card title="Build a plugin" href="/build-plugin">
Author `plugin.json`, backend WASM, frontend bundles, migrations, and release artifacts for catalog publication.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
All `PLUGINS_*` environment variables across API, web gateway, and MCP services.
</Card>
<Card title="Connect MCP server" href="/connect-mcp">
Wire `@paca-ai/paca-mcp` to load dynamically installed MCP plugin bundles.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Stack-wide health checks, auth, Valkey, storage, and MCP connection failures.
</Card>
</CardGroup>
