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.