Plugins
InsightSite has two kinds of plugins, both managed in Admin → Plugins:
| Runtime plugin | Package plugin | |
|---|---|---|
| Shape | one JSON manifest, nuxtcms.plugin.json | a Nuxt layer (npm package / git repo) |
| Can do | settings, injected snippets, CSP sources, event webhooks, scheduled tasks | all of that plus pages, components, APIs, tables |
| Install | online from the admin, no rebuild, works on every deployment target | plugin.mjs add, then rebuild and deploy |
| Typical | analytics tags, live chat, group-robot notifications, search push | membership, shop, form builder |
The official plugin hub
The InsightSite website (https://insightme.top, override with NUXT_PLUGIN_HUB_URL) is the hub and covers the whole flow:
- Sign up at
/account/register. - Bind the site: create an API token under Account → API tokens and paste it into Admin → Plugins → Account & licenses. The token stays on the server.
- Install online: click Install in the marketplace; updates show as "Update to vX".
- Pay with credits: paid plugins are priced in credits. "Buy" spends credits and issues a license for this domain. There is no online payment yet: an administrator tops up credits.
- Request a license: for multi-site licenses, trials or quotes, "Request a license" sends your message and domain to the hub team, who issue a key.
- License binding: licenses are bound to the host of
NUXT_PUBLIC_SITE_URL; a key can also be entered by hand in the plugin's settings. Licenses are re-checked daily by cron. If the hub is unreachable the last result is kept, so a working site never breaks because of an outage.
Unlicensed paid plugins can be installed and configured but stay inactive (no snippets, no webhooks).
Default licenses: domains a hub administrator lists under Plugin hub → Overview → Own sites receive a license key automatically when a paid plugin is installed; installed paid plugins without a key claim one on verify or during the daily check.
When the hub is unreachable (intranet, offline) the marketplace falls back to the official plugins bundled with the framework (plugins/official/*.json); free ones install as usual.
Usage numbers: at most once a day a site tells the hub that it is running (admin update check, cron, upgrade.mjs after a deploy); this feeds the "sites in production" number on the website. Only the host of NUXT_PUBLIC_SITE_URL and the framework version are sent; the hub stores a SHA-256 of the host and verifies a new host once through its /api/health. Local URLs (localhost) never report; set NUXT_HUB_TELEMETRY=false to turn it off.
Runtime manifest
{
"id": "my-plugin",
"name": { "zh": "我的插件", "us": "My plugin" },
"version": "1.0.0",
"requires": ">=1.3.0",
"license": "free",
"settings": [{ "key": "siteId", "label": "Site ID", "type": "text", "required": true }],
"inject": [{ "position": "head", "when": "siteId", "html": "<script src=\"https://cdn.example.com/a.js?id={{settings.siteId}}\" async></script>" }],
"csp": { "script-src": ["https://cdn.example.com"] },
"webhooks": [{ "event": "contact.created", "url": "https://hooks.example.com/x", "body": { "text": "{{event.name}}: {{event.message}}" } }],
"tasks": [{ "name": "Nightly sync", "cron": "0 3 * * *", "url": "{{site.url}}/api/cron/xxx", "headers": { "Authorization": "Bearer {{cronSecret}}" } }]
}
| Field | Notes |
|---|---|
id | lowercase letters, digits, dashes; globally unique |
| text fields | a string or { "zh": "...", "us": "..." } |
version / requires | semver; the marketplace offers updates; requires is the minimum InsightSite version |
license / priceCredits | free or paid (paid plugins only come from the hub) |
settings[].type | text textarea number boolean select secret url; secrets are never sent to browsers |
inject[] | position head / body; scope public (default) / all; when = only if that setting is truthy; raw: true when the setting itself is HTML |
csp | external origins the snippets need, added only on pages where the snippet is actually injected |
webhooks[] | see events below; url, headers, body are templates |
tasks[] | written to Scheduled tasks on install, run by the cron dispatcher, disabled with the plugin, removed on uninstall |
Template variables: {{settings.x}}, {{site.url}}, {{site.name}}, {{event.x}} (webhooks), {{cronSecret}} (tasks). Values are HTML-escaped inside injected snippets.
Events
| Event | When | Payload |
|---|---|---|
contact.created | contact form submitted | name email phone message createdAt |
form.submitted | a visitor sent a form built in Admin → Forms | form (key) name id locale data (field key → answer) |
content.published | post / page / product / content type entry published (including scheduled items going live) | kind id title slug locale status url (entries also collection) |
content.updated | content changed | same |
content.deleted | content removed | same |
plugin.installed | plugin installed or updated | id version updated |
system.error | server error (5xx); the same route and error at most every 10 minutes, 20 an hour | status method path message at |
Webhooks run after the response (waitUntil on Cloudflare); failures are logged only.
For local development, Admin → Plugins → Developer installs a pasted or uploaded manifest; importing again overwrites it and keeps settings.
Package plugins
A regular Nuxt layer with nuxtcms.plugin.json ("kind": "package") at its root, registered from a Nitro plugin:
// server/plugins/my-plugin.ts
import manifest from '../../nuxtcms.plugin.json'
export default defineNitroPlugin(() => {
defineCmsPlugin(manifest, ({ on }) => {
on('content.published', async (payload) => { /* not called while disabled or unlicensed */ })
})
})
Add admin menu items from the layer's app.config.ts: nuxtcms: { adminNav: [{ label, icon, to: '/admin/my-plugin', adminOnly: true, after: 'plugins' }] }.
The admin menu has two levels: main menus (content, products, users & members, SEO & marketing,
extensions, system) with sub-menus. An item joins the main menu of its after item; group
picks one (content, products, users, growth, extensions, system), and
adminNavGroups adds a main menu (the official shop does):
nuxtcms: {
adminNavGroups: [{ key: 'crm', labelKey: 'crm.admin.menu', icon: 'i-lucide-contact', order: 35 }],
adminNav: [{ labelKey: 'crm.admin.leads', icon: 'i-lucide-inbox', to: '/admin/crm/leads', group: 'crm' }],
}
Built-in items a site does not use are hidden with adminNavHide (menu keys such as posts,
media, products); a page outside the nuxtcms admin (e.g. the site's own older admin) joins the
menu with external: true (to used as is, no locale prefix; target: '_blank' opens a new tab);
labels names an item or a main menu per UI locale:
nuxtcms: {
adminNavHide: ['posts', 'pages', 'media', 'products'],
adminNavGroups: [{ key: 'store', label: 'Store', labels: { cn: '商城后台', us: 'Store' }, icon: 'i-lucide-store', order: 5 }],
adminNav: [{ to: '/admin/orders', labels: { cn: '订单', us: 'Orders' }, external: true, target: '_blank', group: 'store' }],
}
The name and logo at the top of the admin menu come from adminBrand: { name, logo, icon } (default InsightSite). To colour the admin in the site's brand, style the cms-admin-sidebar, cms-admin-brand,
cms-admin-header, cms-admin-main classes and the menu's cms-nav-link / cms-nav-group (is-active) in the site's main.css.
A main menu with one visible item shows that item directly. A site with its own admin layout gets
the same menu by passing its links to <AdminSidebarNav :links="visibleLinks" /> (link-class,
active-class and inactive-class apply the site's own styles).
Gate APIs with isPluginActive(event, 'my-plugin') and read settings with getPluginSettings(event, 'my-plugin').
node node_modules/nuxtcms/scripts/plugin.mjs list
node node_modules/nuxtcms/scripts/plugin.mjs add my-plugin --license NCMS-XXXX # by hub id
node node_modules/nuxtcms/scripts/plugin.mjs add github:you/nuxtcms-plugin-x#v1.0.0
node node_modules/nuxtcms/scripts/plugin.mjs remove my-plugin
The CLI installs with the project's package manager and records the plugin in nuxtcms.plugins.json; the base nuxt.config.ts extends every package listed there. A --license key ships with the build and is bound and verified on the first admin visit or cron run.
Official package plugins
shop (paid), saas (paid), ai (paid), builder (free), marketing (free) and blog (free) ship inside the
framework (plugins/shop, plugins/saas, plugins/ai, plugins/builder, plugins/marketing, plugins/blog), so adding
them downloads nothing:
node node_modules/nuxtcms/scripts/plugin.mjs add shop, rebuild, deploy.
See Shop, SaaS kit, AI assistant, Page builder, Marketing tools and Blog toolkit.
Extension points
Package plugins and sites extend the base without copying its files:
| Point | How |
|---|---|
| UI slots | <CmsSlot name> in base pages renders components registered in app.config nuxtcms.slots, e.g. { 'product:aside': { ShopBuyBox: 10 } } (number = order, false hides). Names: header:actions, app:end, post:meta, post:before-content, post:after-content, post:footer, product:aside, product:after-media, product:after-content, product-card:footer, editor:sidebar, account:main, account:aside (SaaS dashboard, ctx.user), account:nav (user area tabs), pricing:after (below the pricing table), page:blocks (shown by the page template instead of the content, ctx.page). Slot components must be global (app/components/global/); ctx is passed as props (post, product, type + contentId in the editor). |
| Credits | A plugin that charges calls cmsCredits(event, 'check' / 'spend', amount, reason); a plugin that keeps balances (the SaaS kit) answers the Nitro hook cms:credits. Without a provider it fails with 503 credits.unavailable. |
| Plugin components in blocks | Register global components in app.config nuxtcms.builderWidgets ({ MyWidget: 'i18n name key' }) and the builder's "Plugin component" block offers them; names not registered do not render. |
| Editor form | Components in editor:sidebar get state (title, content, SEO fields...) from inject('cmsEditor') to fill in suggestions. |
| Editor save hook | A component in editor:sidebar injects cmsEditor and calls onSave(({ id, slug, type }) => ...); it runs after the post / product itself was saved. |
| Public data | onPublicContent((items, { event, kind, detail }) => ...) (server) decorates /api/public/products, /api/public/posts and /api/public/pages/:slug (kind page) responses before they are cached. Product pages emit offers / sku into JSON-LD and hide the inquiry card when hideInquiry is set; post pages use related when present. Only catalogue data: responses are cached. |
| Own tables | Drizzle schemas per dialect plus ensurePluginSchema(event, id, ddl), which runs idempotent DDL once per isolate ({prefix} = table prefix). node scripts/plugin-ddl.mjs <id> generates the DDL for the official plugins. |
| Events | order.created, order.paid, order.status_changed, comment.created, subscriber.created, saas.payment.paid (a SaaS kit payment: number plan period amount amountFormatted currency provider email name url) join the events below. |
Develop a plugin against the framework repo with NUXT_PACKAGE_PLUGINS=./plugins/shop,./plugins/blog,./plugins/saas nuxt dev.
nuxt dev does not enforce licenses. Plugin pages live in node_modules/nuxtcms/plugins, so a site's
main.css needs @source "../../../node_modules/nuxtcms/plugins"; (upgrade.mjs adds it).
Publishing to the hub
Send the manifest (runtime) or package spec (package) to an administrator, who lists and prices it under Hub → Plugins on the website.