Overview
OpeNext plugins are installed from a ZIP via Dashboard → Plugins. Packages can be frontend-only (HTML, CSS, JavaScript, or TypeScript in the browser) or full-stack (frontend + api/ folder with REST APIs, models, and migrations).
Frontend plugin kinds: block plugins for the page editor, system (dashboard) plugins for admin apps, and hybrid plugins that do both.
For the full reference (manifest fields, upload lifecycle, security), read the Plugin Upload docs.
JavaScript or TypeScript
You can write a plugin in JavaScript or TypeScript. Upload .js files directly, or reference .ts and .tsx files from the manifest. OpeNext CMS transpiles those TypeScript files to JavaScript during upload.
// manifest.json
{
"id": "crm-plugin",
"name": "CRM",
"version": "1.0.0",
"kind": "system",
"adminEntry": "ui/admin/index.html",
"serverEntry": "api/index.ts",
"blockEntry": "ui/block/index.ts"
}crm-plugin/
└── crm-plugin/
├── manifest.json
├── ui/
│ ├── admin/
│ │ ├── index.html
│ │ └── app.ts
│ └── block/
│ └── index.ts
└── api/
├── index.ts
└── routes/
└── customers.ts- Use .js for a plugin with no compile step
- Use .ts or .tsx for type safety; the original source is retained and JavaScript runtime files are generated
- Simple plugin modules, routes, services, models, hooks, and blocks can use TypeScript
- Build React/Vue applications before upload because the uploader transpiles but does not bundle npm imports
What files you need
Block plugin (page editor only)
my-block/
├── manifest.json Required
├── index.js Required — mount script
└── style.css Optional
System / dashboard plugin (frontend-only)
my-crm/
├── manifest.json Required
└── admin/
├── index.html Required — iframe entry
├── app.js Recommended
└── style.css Optional
Full-stack system plugin (frontend + backend)
my-crm/
├── manifest.json
├── admin/
│ └── index.html
└── api/
├── index.js or index.ts Required — backend entry
├── routes/ REST route files (auto-loaded)
├── models/ Mongoose schemas
├── migrations/ Database migrations
└── hooks/ install.js / uninstall.js
Hybrid (dashboard + page block)
my-plugin/
├── manifest.json
├── admin/
│ └── index.html
└── block/
├── index.js
└── style.cssStep 1 — Block plugin from scratch
Create a testimonial block editors can add to any page.
// manifest.json
{
"id": "testimonial-block",
"name": "Testimonial Block",
"version": "1.0.0",
"kind": "block",
"type": "testimonial",
"icon": "💬",
"entryPoint": "index.js",
"styles": ["style.css"]
}// index.js
window.__NEXTCMS_PLUGINS__ = window.__NEXTCMS_PLUGINS__ || {};
window.__NEXTCMS_PLUGINS__['testimonial-block'] = {
mount(element, context) {
var data = (context.block || {}).data || {};
element.innerHTML = '';
var quote = document.createElement('blockquote');
quote.textContent = data.quote || 'Your testimonial';
var author = document.createElement('p');
author.textContent = data.author || 'Customer name';
element.append(quote, author);
},
unmount(element) {
element.innerHTML = '';
}
};The registry key testimonial-block must exactly match id in the manifest.
Step 2 — Dashboard (system) plugin
Build a CRM-style admin app that opens in the dashboard sidebar.
// manifest.json
{
"id": "crm-plugin",
"name": "CRM",
"version": "1.0.0",
"kind": "system",
"type": "crm",
"icon": "👥",
"adminEntry": "admin/index.html"
}For small JSON storage without a backend, use the plugin-data API from admin/app.js:
fetch('/api/plugins/crm-plugin/data/contacts', { credentials: 'include' })
.then(function (res) { return res.json(); })
.then(function (payload) {
var contacts = payload.data?.data || [];
// render contacts
});The CMS loads admin/index.html in a sandboxed iframe. Always use relative asset paths (./app.js).
Step 3 — Full-stack backend plugin
Add an api/ folder for REST APIs, database models, and migrations. APIs are mounted at /api/plugins/{pluginId}/* automatically.
// manifest.json (backend fields)
{
"id": "crm-plugin",
"name": "CRM",
"version": "1.0.0",
"kind": "system",
"adminEntry": "admin/index.html",
"serverEntry": "api/index.ts",
"migrations": ["api/migrations"],
"permissions": ["crm.read", "crm.write"],
"installHook": "api/hooks/install.ts",
"dependencies": ["pdfkit"]
}Runtime npm packages (for example pdfkit) are listed in dependencies. Upload merges them into the CMS root package.json; run npm install at the CMS root, restart, then enable the plugin.
// server/index.js
module.exports = {
register({ app, cms }) {
cms.logger.info('CRM backend registered');
},
boot({ cms }) {},
shutdown() {}
};
// server/routes/customers.js — auto-loaded
module.exports = (app) => {
app.get('/customers', async (req, res) => {
res.json({ customers: [] });
});
};// admin/app.js — call your plugin API
fetch('/api/plugins/crm-plugin/customers', { credentials: 'include' })
.then(function (res) { return res.json(); })
.then(function (payload) {
// render payload.customers
});On upload, the CMS runs migrations, the install hook, registers permissions, and loads routes. Handlers receive req.user and req.cms automatically.
Step 4 — Hybrid plugin
Add a page-editor block alongside your dashboard app.
{
"id": "crm-plugin",
"name": "CRM",
"version": "1.0.0",
"kind": "system",
"type": "crm",
"adminEntry": "admin/index.html",
"blockEntry": "block/index.js",
"styles": ["block/style.css"]
}Step 5 — Build with React or Vite
Build frontend applications before zipping. Simple plugin TypeScript files are compiled automatically during upload.
// vite.config.ts
export default defineConfig({
base: './',
build: {
outDir: 'crm-plugin/admin',
emptyOutDir: true
}
});- Run npm run build to produce admin/index.html + hashed JS/CSS
- Copy manifest.json into the output folder
- Use .ts files directly for simple api/ modules, routes, services, models, migrations, and hooks
- List backend npm deps in manifest.json dependencies — upload merges them into CMS package.json
- Do not include node_modules/, src/, or .env in the ZIP
Step 6 — Package and upload
# Zip contents so manifest.json is at the root
Compress-Archive -Path .\crm-plugin\* -DestinationPath .\crm-plugin.zip -Force- Dashboard → Plugins → Upload Plugin
- Upload merges manifest dependencies into CMS root package.json
- Run npm install at CMS root, restart, then enable the plugin
- To update: remove old plugin, bump version, re-upload with same id
Best practices
- Keep the plugin id stable — it is the registry key and API namespace
- Use textContent for user data — avoid unsafe innerHTML
- Never embed secrets in browser files
- Use credentials: 'include' for all fetch calls to CMS APIs
- Build React/Vue frontend applications before upload — the uploader transpiles simple TypeScript but does not bundle JSX imports
- Use plugin-data API for small JSON; use api/ routes for relational data
- Do not register backend routes at /data/* — reserved for CMS plugin-data API
Full Plugin Upload docs → · Contributing guide → · Examples on GitHub →