jChat LogojChat Docs
Addon Development

SDK Contracts & Manifest

Deep dive into contracts, permissions, and UI extension mount point definitions.

Defining the Addon Manifest

The manifest declares addon identification, compatibility ranges, UI mount points, server bundles, and role-based permissions in src/manifest.ts:

import type { AddonManifest } from "@jchat/addon-sdk"

const manifest: AddonManifest = {
  id: "pollwise",
  name: "Pollwise",
  version: "1.0.0",
  description: "Create polls and gather votes from users in the chat.",
  author: "JChat Team",
  publisher: { id: "jchat" },
  compatibility: {
    jchat: ">=1.0.0 <2.0.0",
    addonSdk: "^1.0.0"
  },
  locales: [
    "ar",
    "bg",
    "de",
    "el",
    "en",
    "es",
    "fr",
    "he",
    "hi",
    "hr",
    "kn",
    "ml",
    "nl",
    "pt",
    "ro",
    "ru",
    "ta",
    "te",
    "tr",
    "ur"
  ],
  defaultLocale: "en",
  frontend: {
    mountPoints: ["chat-toolbar", "chat-plus-menu", "owner-settings", "addon-settings"],
    bundle: "./ui.js"
  },
  server: {
    bundle: "./api.ts",
    migrations: ["migrations/001_create_polls.sql"]
  },
  accessPermission: "addon.pollwise.vote",
  permissions: ["addon.pollwise.vote", "addon.pollwise.create", "addon.pollwise.manage"],
  permissionDefinitions: [
    {
      name: "addon.pollwise.vote",
      label: "Vote in Polls",
      description: "Vote in polls"
    },
    {
      name: "addon.pollwise.create",
      label: "Create Polls",
      description: "Create polls in the chat"
    },
    {
      name: "addon.pollwise.manage",
      label: "Manage Polls",
      description: "Close or delete any poll"
    }
  ]
}

export default manifest

Frontend UI Component

The frontend React entrypoint lives in src/ui.tsx. It receives AddonProps (mountPoint, siteId, roomId) and uses @jchat/addon-sdk hooks:

import {
  MOUNT_POINTS,
  useAddonSDK,
  useAddonQuery,
  useAddonMutation,
  useTranslation,
  type AddonProps
} from "@jchat/addon-sdk"
import Icons from "@jchat/addon-sdk/icon"
import {
  Button,
  Input,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Switch
} from "@jchat/addon-sdk/ui"
import locales from "./locales"
import "./tailwind.css"

export default function Pollwise({ mountPoint, siteId, roomId }: AddonProps) {
  const sdk = useAddonSDK()
  const { t, dir } = useTranslation(locales)

  // 1. Render Admin Settings panels
  if (mountPoint === "owner-settings" || mountPoint === "addon-settings") {
    return <PollwiseSettings siteId={siteId} />
  }

  // 2. Fetch data via TanStack Query bridge scoped to /api/v1/addons/<addonId>
  const { data, isLoading } = useAddonQuery<{ data: any[] }>({
    path: "/polls"
  })

  // 3. Render Composer Toolbar / Plus Menu action
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          aria-label={t("addon_title")}
          className="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground"
        >
          <Icons.BarChart3 className="h-4 w-4" />
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-80 p-4">
        <h4 className="font-semibold text-foreground">{t("create_poll")}</h4>
        {/* Interactive Poll Form */}
      </PopoverContent>
    </Popover>
  )
}

Supported UI Mount Points

Addons can inject React components into multiple layout slots declared in their manifest:

Mount PointLocationTypical Use Case
chat-toolbarComposer toolbarQuick action buttons (Polls, Giphy, Soundboard)
chat-plus-menuComposer (+) menuExtended tools, games, dice rolls, attachments
chat-sidebarRight chat drawerInteractive feeds, participant widgets, active games
chat-headerRoom top headerChannel badges, livestream links, status alerts
owner-settingsOwner Control PanelMaster credentials, global API keys, licensing
addon-settingsSite Admin DashboardSite-level preferences and feature switches
emoji-picker-gif-tabEmoji/Media modalDedicated GIF search tabs (GifYard)
emoji-picker-sticker-tabEmoji/Media modalCustom sticker collections (Stickerly)
message-context-menuMessage dropdownMessage translation, bookmarking, moderation

Backend Server Factory (src/api.ts)

Backend-bearing addons export a default factory function that receives AddonServerContext and an Elysia AddonApp instance:

import { verifyAddonEntitlement, type AddonApp, type AddonServerContext } from "@jchat/addon-sdk"
import type { MyAddonDatabase } from "./db"

export default (
  {
    db,
    cache,
    emit,
    t,
    verifyEntitlement,
    getConfig,
    getUser,
    hasPermission
  }: AddonServerContext<MyAddonDatabase>,
  app: AddonApp
) => {
  // Commercial addons: verify entitlement directly
  verifyAddonEntitlement("my-addon", verifyEntitlement)

  return app.get("/items", async ({ user }) => {
    const items = await db
      .selectFrom("my_addon_items")
      .where("siteId", "=", user?.siteId ?? "")
      .selectAll()
      .execute()

    return { items }
  })
}

AddonServerContext Members

MemberTypeDescription
dbAddonDatabase<T>Type-safe, schema-scoped query builder. Tables live in "addons" and use the <addonId>_ prefix.
verifyEntitlement(id?: string) => booleanVerifies that the host holds an active commercial license entitlement for this addon.
cacheAddonCacheClientNamespaced Redis key-value cache client (get, set, del, incr, expire, ttl).
emit(event, payload, target?) => Promise<void>Real-time WebSocket event broadcaster across cluster rooms or sites.
ttypeof tHost-injected TypeBox schema builder for validating body, query, params, and headers.
getConfig(siteId: string) => Promise<Record<string, unknown>>Retrieves configured addon settings for a given site.
getUser(userId: string) => Promise<AddonUser | null>Retrieves public profile data for a specific user.
getUsers(userIds: string[]) => Promise<Map<string, AddonUser>>Batch-fetches multiple user profiles in a single query.
hasPermission(user, permission) => booleanChecks whether the request user holds a given JChat or custom addon permission.

On this page