Plugins
Plugins are the primary extension mechanism in Popii. They tap into the client lifecycle, augment the pop context with new methods, register slash commands, add web routes, and more — all from a single plugins: [] array in your config.
Adding plugins
Section titled “Adding plugins”import { popiiClient, sqlitePlugin, webPlugin, errorHandlerPlugin, voicePlugin,} from "popii-framework";
const client = popiiClient({ token: process.env.DISCORD_TOKEN!, plugins: [ sqlitePlugin(), // must come before plugins that depend on it voicePlugin(), webPlugin({ port: 3000 }), errorHandlerPlugin(), ],});Built-in plugins
Section titled “Built-in plugins”Data & storage
Section titled “Data & storage”| Plugin | Purpose |
|---|---|
sqlitePlugin | Bun SQLite database in every pop |
mongoosePlugin | MongoDB via Mongoose |
Bot features
Section titled “Bot features”| Plugin | Purpose |
|---|---|
levelsPlugin | Per-guild XP and level ranking |
economyPlugin | Virtual currency, daily rewards, leaderboard |
gamesPlugin | Coinflip, slots, RPS, and blackjack |
moderationPlugin | Warn, kick, ban, timeout, case history |
voicePlugin | Music playback, queue, SponsorBlock |
deskPlugin | Support ticket system |
giveawayPlugin | Timed giveaways |
pollPlugin | Button-based polls |
starboardPlugin | Star-reaction highlights |
welcomePlugin | Welcome and goodbye messages |
autorolePlugin | Auto-assign roles on join |
rssPlugin | RSS/Atom feed posting |
notifyPlugin | Twitch stream and YouTube upload alerts |
remindersPlugin | User-set time-based reminders |
snapshotsPlugin | Periodic server activity snapshots |
lastFmPlugin | Last.fm scrobbling and stats |
Infrastructure & utilities
Section titled “Infrastructure & utilities”| Plugin | Purpose |
|---|---|
webPlugin | HTTP server, dashboard, OAuth2 |
uiPlugin | Pagination, modals, wizards, prompts |
popiiAiPlugin | AI text generation (OpenAI, Gemini, Anthropic) |
autoModPlugin | AI-powered message moderation |
payPlugin | Patreon and Ko-fi webhooks |
captchaPlugin | hCaptcha verification |
canvasPlugin | Image generation |
activityRotatorPlugin | Rotating bot status |
webhookPlugin | Inbound webhooks with HMAC validation |
gdprPlugin | User data deletion command |
Developer tools
Section titled “Developer tools”| Plugin | Purpose |
|---|---|
errorHandlerPlugin | User-friendly error replies |
permissionGuardPlugin | Enforce command permission fields |
commandLoggerPlugin | Execution logging |
commandAnalyticPlugin | In-memory usage counters |
telemetryPlugin | Error capture with Sentry/Datadog support |
reloadPlugin | Owner-only /reload command |
sandboxPlugin | Isolated test environment |
pluginManagerPlugin | Runtime plugin management |
Community plugins
Section titled “Community plugins”Install from the registry with the CLI:
bunx popii add economy # installs popii-plugin-economybunx popii search music # search the registryOr enable auto-discovery to automatically load any installed popii-plugin-* package:
const client = popiiClient({ token: process.env.DISCORD_TOKEN!, autoDiscover: true, pluginConfig: { "popii-plugin-economy": { currencySymbol: "🪙" }, },});Writing a plugin
Section titled “Writing a plugin”A plugin is a plain object with a name and optional lifecycle hooks:
import type { PopiiPlugin } from "popii-framework";
export function analyticsPlugin(options: { dsn: string }): PopiiPlugin { return { name: "analytics",
setup(client) { // Called once before Discord connects. // Register listeners, initialize state, create database tables. client.global.on("xp-gained", ({ userId, amount }) => { trackEvent("xp_gained", { userId, amount }); }); },
ready(client) { // Called after the bot is online. // Safe to access client.discord.guilds.cache here. console.log(`Analytics active for ${client.discord.guilds.cache.size} guilds`); },
cleanup(client) { // Called on graceful shutdown. // Flush buffers, close connections. }, };}Lifecycle hooks
Section titled “Lifecycle hooks”| Hook | When | Notes |
|---|---|---|
setup(client) | Before Discord connects | Safe to register listeners; client.discord not yet ready |
ready(client) | After Discord connects | client.discord.guilds.cache is populated |
reload(client) | After a hot-reload | Re-apply middleware wrapping if needed |
cleanup(client) | On graceful shutdown | Clear intervals, flush buffers, close sockets |
onCommandExecute(pop, cmd) | Before every command | Good for analytics and logging |
onCommandError(err, pop) | When a command throws | Good for error reporting services |
onContextCreate(pop) | When a pop context is created | Inject data into context before the handler runs |
Shipping commands with a plugin
Section titled “Shipping commands with a plugin”Plugins can bundle their own slash commands. The framework registers them after user commands are loaded, and user commands always take priority — a file at src/commands/greet.ts silently overrides any plugin command named greet.
import type { PopiiPlugin } from "popii-framework";
export function supportPlugin(): PopiiPlugin { return { name: "support", commands: [ { name: "ticket", description: "Open a support ticket", async do(pop) { await pop.reply("Opening your ticket..."); }, }, ], // setup() is optional for command-only plugins };}Letting bot owners customize plugin commands
Section titled “Letting bot owners customize plugin commands”Users can suppress or patch individual plugin commands from the top-level config without touching the plugin source:
const client = popiiClient({ plugins: [supportPlugin()], pluginCommands: { disable: ["ticket"], // don't register this command overrides: [ { name: "ticket", description: "Need help? Open a ticket." }, // patch a field ], },});The plugin’s do handler is preserved in overrides — only the declared fields are replaced.
Exposing dashboard settings
Section titled “Exposing dashboard settings”Declare a settingsSchema to add fields to the web dashboard’s per-server settings form automatically:
export function greetPlugin(): PopiiPlugin { return { name: "greet", settingsSchema: [ { id: "greetChannel", label: "Greeting Channel", type: "channel", description: "Where to post welcome messages", category: "Greet Plugin", }, { id: "greetEnabled", label: "Enable greetings", type: "boolean", default: true, category: "Greet Plugin", }, ], setup(client) { /* ... */ }, };}Publishing to the registry
Section titled “Publishing to the registry”- Clone the plugin template
- Name your package
popii-plugin-<name> - Add a
"popii"metadata block topackage.json:
{ "name": "popii-plugin-soundboard", "popii": { "displayName": "Soundboard", "description": "Play sound effects in voice channels", "category": "voice", "export": "soundboardPlugin" }}