Plugins

Plugins

InsightSite has two kinds of plugins, both managed in Admin → Plugins:

Runtime pluginPackage plugin
Shapeone JSON manifest, nuxtcms.plugin.jsona Nuxt layer (npm package / git repo)
Can dosettings, injected snippets, CSP sources, event webhooks, scheduled tasksall of that plus pages, components, APIs, tables
Installonline from the admin, no rebuild, works on every deployment targetplugin.mjs add, then rebuild and deploy
Typicalanalytics tags, live chat, group-robot notifications, search pushmembership, 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:

  1. Sign up at /account/register.
  2. 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.
  3. Install online: click Install in the marketplace; updates show as "Update to vX".
  4. 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.
  5. 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.
  6. 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}}" } }]
}
FieldNotes
idlowercase letters, digits, dashes; globally unique
text fieldsa string or { "zh": "...", "us": "..." }
version / requiressemver; the marketplace offers updates; requires is the minimum InsightSite version
license / priceCreditsfree or paid (paid plugins only come from the hub)
settings[].typetext 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
cspexternal 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

EventWhenPayload
contact.createdcontact form submittedname email phone message createdAt
form.submitteda visitor sent a form built in Admin → Formsform (key) name id locale data (field key → answer)
content.publishedpost / page / product / content type entry published (including scheduled items going live)kind id title slug locale status url (entries also collection)
content.updatedcontent changedsame
content.deletedcontent removedsame
plugin.installedplugin installed or updatedid version updated
system.errorserver error (5xx); the same route and error at most every 10 minutes, 20 an hourstatus 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:

PointHow
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).
CreditsA 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 blocksRegister 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 formComponents in editor:sidebar get state (title, content, SEO fields...) from inject('cmsEditor') to fill in suggestions.
Editor save hookA component in editor:sidebar injects cmsEditor and calls onSave(({ id, slug, type }) => ...); it runs after the post / product itself was saved.
Public dataonPublicContent((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 tablesDrizzle 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.
Eventsorder.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.