Build extensions
Overview Package layout extension.json Service provider Migrations Permissions Settings Frontend (window.Convoro) Admin pages Packaging & installing Full example

# Building extensions

🤖 Build it with AI. Convoro is AI-native — point your AI assistant at `convoro.co/llms.txt` (or the JSON at `/api/ai/spec.json`) and it has everything it needs — manifest schema, slots, theme tokens, the `window.Convoro` API, and the publish flow — to scaffold a correct, installable extension or theme on the first try.

A Convoro extension is a self-contained folder. There is no build step, no Composer requirement, and no `dump-autoload` — you zip the folder and upload it in Admin → Marketplace, or drop it in the `extensions/` directory. Convoro discovers it, autoloads its PHP, runs its migrations on enable, and injects its prebuilt frontend bundle.

## Package layout

    my-extension/
    ├─ extension.json          # manifest (required)
    ├─ src/
    │  └─ Extension.php        # a Laravel ServiceProvider (optional)
    ├─ migrations/             # standard Laravel migrations (run on enable)
    │  └─ 2026_01_01_000000_create_my_table.php
    └─ assets/
       ├─ forum.js             # prebuilt ESM, injected on the forum (optional)
       └─ admin.js             # prebuilt ESM, injected in admin (optional)

## The manifest — `extension.json`

    {
      "id": "acme-hello",                       // unique; folder-safe
      "name": "Hello",
      "version": "1.0.0",
      "description": "Adds a friendly hello.",
      "author": "Acme",
      "convoro": ">=0.1.0",                      // version constraint
      "type": "extension",                      // "extension" | "theme"
      "namespace": "Acme\\Hello\\",             // PSR-4 root for src/
      "provider": "Acme\\Hello\\Extension",     // a ServiceProvider FQCN (optional)
      "migrations": "migrations",               // dir to run on enable (optional)
      "permissions": [                           // merged into the group editor
        { "key": "hello.use", "label": "Use Hello", "category": "Hello", "baseline": true }
      ],
      "settings": [                              // rendered as a form on the Marketplace card
        { "key": "greeting", "label": "Greeting", "type": "text", "default": "Hi" }
      ],
      "assets": { "forum": "assets/forum.js" }, // prebuilt bundles to inject
      "admin_url": "/admin/ext/hello"           // optional management page link
    }

Only `id` and `name` are required. `convoro` accepts `*`, exact versions, `>=`/`<=`/`>`/`<`, `^` and `~` ranges.

## Service provider

If your extension needs backend behavior, point `provider` at a normal Laravel `ServiceProvider` under `src/`. It boots in the standard lifecycle with full framework access — register routes, bindings, events, anything.

    <?php
    namespace Acme\Hello;

    use Illuminate\Support\Facades\Route;
    use Illuminate\Support\ServiceProvider;

    class Extension extends ServiceProvider
    {
        public function boot(): void
        {
            Route::middleware('web')->get('/api/ext/hello', function () {
                return response()->json(['message' => 'Hello from an extension!']);
            });
        }
    }

Routes registered in `boot()` are picked up by `route:cache` too — they work in production, not just locally.

## Migrations

Drop standard Laravel migrations in `migrations/`. They run automatically when the extension is enabled, and roll back on uninstall. Use `Schema::hasTable()` guards so re-enabling is safe.

## Permissions

Declare permissions in the manifest and they appear in Admin → Members → groups automatically. Set `"baseline": true` to grant to every signed-in member. Check them in code with `$user->hasPermission('hello.use')`.

## Settings

List `settings` in the manifest and Convoro renders a form when an admin clicks your extension's card. Field `type` can be `text`, `textarea`, `boolean`, `number`, `select` (with `options`), or `color`. Read values at runtime:

    use App\Support\ExtensionManager;

    $greeting = ExtensionManager::setting('acme-hello', 'greeting', 'Hi');

## Frontend — `window.Convoro`

Ship a prebuilt ESM file (no build step on the server) under `assets/` and list it in `assets.forum` / `assets.admin`. Convoro injects it for enabled extensions. It hooks into the UI through the global `window.Convoro` API:

    const c = window.Convoro;

    // Render into a named slot. Provide a mount callback (gets the DOM element),
    // an html string, or a Vue component.
    c.registerSlot('topic:below', {
      ext: 'acme-hello',
      mount(el) {
        el.innerHTML = '<div>👋 Hello from my extension</div>';
      },
    });

    // Cross-extension event bus
    c.on('something', (payload) => { /* … */ });
    c.emit('something', { ok: true });

Available slots: `header:end` (forum header, right side), `forum:footer` (below every page), `topic:below` (under the opening post; receives `{ topicId, slug }`). Slots are reactive, so an extension that finishes loading after the page still appears. Your bundle is served from `/ext-asset/{id}/forum`.

## Admin pages

For richer management, register an authenticated route in your provider and point `admin_url` at it — the Marketplace shows an "Open management page" link. Gate it with the `auth` + `admin` middleware:

    Route::middleware(['web', 'auth', 'admin'])
        ->get('/admin/ext/hello', fn () => response('<!doctype html>…'));

## Publishing from GitHub

Convoro has a Packagist-style registry: you publish an extension by linking its public GitHub repo — no zip uploads, no Packagist account.

1
Put your extension in a public GitHub repo with `extension.json` at the repo root.
2
Cut a GitHub release (tag) for each version — Convoro installs the latest release (and falls back to the default branch if there are none).
3
Submit the repo to the Convoro catalog (or, as the store owner, link it in Admin → Store → Publish from GitHub). Convoro reads your manifest and lists it.
4
It now appears in everyone's Admin → Marketplace → catalog; a one-click install pulls your release archive straight from GitHub, runs migrations, and loads your frontend bundle.

Push a new release and admins just hit Refresh (or re-install) to update. Premium add-ons distribute through the store with a license key instead.

## Worked example

The first-party extensions (Announcement Bar, Horizon, Analytics, and the Members Online / Online Now / Trending Topics widgets) are the best references — each exercises a provider, and variously a migration, a permission, settings/admin pages, and a frontend slot widget.

Building something to sell? Premium extensions distribute through the Convoro store with a license key; buyers redeem it in their own Marketplace.