Appearance
main.js — Backend Routes
main.js is the backend entry point for your EA plugin. It runs on the server side and lets you define Express routes, access the plugin database, protect endpoints, and optionally bootstrap a full NestJS application.
To enable it, add "main": "main.js" to your setting.json:
json
{
"versionNumber": "0",
"version": "1.0.0",
"name": "my-plugin",
"init": true,
"type": "EA",
"main": "main.js",
"script": "script.js",
"id": "unique-plugin-id"
}Function Signature
main.js must export a function that receives a context object:
js
module.exports = ({ server, Router, app, verifyToken, databasePath, users, io, emitter, roles, JwtAuthGuard, GetUser, package, getSocket }) => {
// your backend logic
}Context Parameters
| Parameter | Type | Description |
|---|---|---|
server | Express | The Express server instance. Mount your routes on it |
Router | function | Express Router constructor for creating route groups |
app | object | Plugin utilities — database access, helpers (see below) |
verifyToken | middleware | Express middleware that validates the user's auth token. Use on protected routes |
databasePath | string | Absolute path to the plugin's database/ folder |
users | object | Access to the platform's users service |
io | Socket.IO | Socket.IO server instance for real-time events |
emitter | EventEmitter | Platform-wide event bus. Use emitter.on(...) to react to core events (e.g. a role being deleted) |
roles | object | Access to the platform's roles/permissions service |
JwtAuthGuard | class | NestJS auth guard, for plugins that bootstrap a NestJS app (see Pattern 2 below) |
GetUser | decorator | NestJS param decorator that resolves the authenticated user, for use alongside JwtAuthGuard |
package() | function | Returns the platform's own package.json metadata (e.g. version) |
getSocket() | function | Returns the active Socket.IO connection helper, for emitting to specific clients/rooms |
app Utilities
app.database(name)
Returns a database accessor for the given database namespace (folder name inside database/).
js
const db = app.database("my-plugin");.readJson(path[])
Reads and parses a JSON file. Path is an array of path segments relative to the database namespace folder.
js
const data = app.database("my-plugin").readJson(["data.json"]);.writeJson(path[], data)
Serializes and writes data to a JSON file.
js
app.database("my-plugin").writeJson(["data.json"], data);app.getRndString()
Generates a random unique string, useful for creating record IDs.
js
const id = app.getRndString();
// → "_a3f92bc1d4"Pattern 1 — Simple Express Router
Use this pattern for lightweight REST endpoints. All routes are mounted under /api/<your-prefix>.
js
module.exports = ({ server, Router, app, verifyToken }) => {
let API = Router();
// Public route
API.post("/contact", (req, res) => {
let data = app.database("my-plugin").readJson(["contact.json"]);
data.unshift(req.body);
app.database("my-plugin").writeJson(["contact.json"], data);
res.json(true);
});
// Protected routes (require auth token)
API.get("/items/:id", (req, res) => {
let data = app.database("my-plugin").readJson(["items.json"]);
res.json(data[req.params.id]);
})
.post("/items", verifyToken, (req, res) => {
let data = app.database("my-plugin").readJson(["items.json"]);
let body = req.body;
body.id = app.getRndString();
data[body.id] = body;
app.database("my-plugin").writeJson(["items.json"], data);
res.json(true);
})
.put("/items/:id", verifyToken, (req, res) => {
let data = app.database("my-plugin").readJson(["items.json"]);
data[req.params.id] = req.body;
app.database("my-plugin").writeJson(["items.json"], data);
res.json(true);
})
.delete("/items/:id", verifyToken, (req, res) => {
let data = app.database("my-plugin").readJson(["items.json"]);
delete data[req.params.id];
app.database("my-plugin").writeJson(["items.json"], data);
res.json(true);
});
server.use("/api/my-plugin", API);
}Routes are available at
/api/my-plugin/items,/api/my-plugin/contact, etc.
Pattern 2 — NestJS Bootstrap
For complex plugins, you can bootstrap a full NestJS application from main.js. The NestJS app is built separately and its compiled output is placed in database/backend/.
js
const { join } = require("path");
module.exports = async ({ server, databasePath, app, users, io }) => {
const backendPath = join(databasePath, "backend", "dist", "src");
const { bootstrap } = require(join(backendPath, "main"));
// Set environment variables your NestJS app needs
process.env['DATABASE_PATH'] = databasePath;
process.env['APP_SECRET'] = "your-secret";
return await bootstrap({
server,
app,
users,
io,
databasePath
});
};NestJS File Structure
When using the NestJS pattern, your plugin's database/ folder holds the compiled backend output:
my-plugin/
├── 1.0.0/
│ ├── setting.json
│ ├── script.js
│ └── main.js ← bootstraps the NestJS app
└── database/
└── my-plugin/
└── backend/
└── dist/
└── src/
└── main.js ← compiled NestJS entry pointThe NestJS project lives outside the plugin folder and is developed separately. Only the compiled
dist/output is placed indatabase/backend/when deploying.
Using verifyToken
Add verifyToken as middleware on any route that requires the user to be authenticated:
js
// Public — no auth required
API.get("/public-data", (req, res) => { ... });
// Protected — must send a valid token
API.post("/private-data", verifyToken, (req, res) => { ... });
API.put("/private-data/:id", verifyToken, (req, res) => { ... });
API.delete("/private-data/:id", verifyToken, (req, res) => { ... });Requests to protected routes without a valid token receive a 401 Unauthorized response automatically.
Listening to Platform Events
emitter is a shared Node.js EventEmitter used across the platform and its plugins. Use it to react when something changes elsewhere in the app — for example, cleaning up your plugin's own data when a role is deleted:
js
module.exports = ({ app, emitter }) => {
emitter.on("[roles]", (deletedRole) => {
const data = app.database("my-plugin").readJson(["config.json"]);
delete data.roleOverrides?.[deletedRole.id];
app.database("my-plugin").writeJson(["config.json"], data);
});
}The packages Field
If your main.js needs npm dependencies (e.g. nodemailer, axios), don't require() them assuming they're already installed — declare them in setting.json instead:
json
{
"name": "my-plugin",
"main": "main.js",
"packages": [
{ "name": "nodemailer", "version": "6.9.2" }
]
}These are installed automatically (via live-plugin-manager) when the plugin is built/loaded, so you don't need to vendor node_modules yourself or assume the host app already has them.
Database JSON Structure
Design your database/ JSON files to match what your routes read and write. Example for a simple items store:
json
{
"_a3f92bc1d4": {
"id": "_a3f92bc1d4",
"name": "Item One",
"price": 25
},
"_b7e01cd2f5": {
"id": "_b7e01cd2f5",
"name": "Item Two",
"price": 40
}
}Using an object keyed by ID (from app.getRndString()) makes lookups, updates, and deletes by ID fast and straightforward.