Appearance
EA Development Guide
Prerequisites
- Node.js 18+
- Motanamy CLI (
npm install -g motanamy-cli) - A running Motanamy EA instance for testing
File Structure
Every EA plugin follows this structure:
my-plugin/
├── 1.0.0/ ← version folder
│ ├── setting.json ← plugin manifest
│ ├── script.js ← frontend registration script (runs on load)
│ ├── main.js ← backend routes & server logic (optional)
│ ├── widget.vue ← dashboard widget component (optional)
│ ├── setting.vue ← settings page component (optional)
│ ├── README-AR.md ← Arabic documentation
│ ├── README-EN.md ← English documentation
│ └── images/ ← plugin images/icons
└── database/
└── my-plugin/
└── data.json ← initial database stateWhen you release a new version, add a new version folder alongside 1.0.0/:
my-plugin/
├── 1.0.0/
├── 1.1.0/ ← new version
└── database/setting.json
The manifest file tells EA how to load your plugin.
json
{
"versionNumber": "0",
"version": "1.0.0",
"name": "my-plugin",
"init": true,
"type": "EA",
"main": "main.js",
"assets": "assets",
"script": "script.js",
"id": "unique-plugin-id"
}| Field | Type | Description |
|---|---|---|
versionNumber | string | Internal version counter, increments on each update |
version | string | Semantic version string |
name | string | Plugin identifier (used in file paths) |
init | boolean | Whether the plugin initializes on EA startup |
type | "EA" | Platform type |
script | string | Frontend entry point filename |
main | string | Backend entry point filename (optional, e.g. "main.js") |
assets | string | Assets folder name (optional, e.g. "assets") |
id | string | Unique plugin ID |
script.js
This is the plugin's registration file. It runs when EA loads and uses the plugins object to wire components into the platform.
js
plugins["my-plugin"] = function () {
// Register translations
this.addLanguage("en", "myPlugin", { name: "My Plugin" });
this.addLanguage("ar", "myPlugin", { name: "الإضافة" });
// Define roles
this.addRole({
id: "my-plugin",
text: "My Plugin",
key: "myPlugin.name",
roles: {
create: { text: "Create", key: "button.create", value: false },
delete: { text: "Delete", key: "button.delete", value: false }
}
});
// Mount a dashboard widget
this.addTask({
name: "/[my-plugin]widget.vue",
id: "init-my-plugin",
file: ["my-plugin", "widget.vue"]
});
// Add a Settings sidebar entry
this.addToSetting({
label: "My Plugin",
key: "myPlugin.name",
icon: "mdi mdi-puzzle",
to: "/app/setting/my-plugin"
});
// Register the settings page route
this.addPage({
name: "/[my-plugin]setting.vue",
path: "/app/setting/my-plugin",
file: ["my-plugin", "setting.vue"]
}, "setting");
}See the full script.js API Reference for all available methods.
main.js
The optional backend file. Exports a function that receives the Express server, Router, database utilities, and auth middleware. Use it to define REST API routes for your plugin.
js
module.exports = ({ server, Router, app, verifyToken }) => {
let API = Router();
API.get("/data", (req, res) => {
res.json(app.database("my-plugin").readJson(["data.json"]));
});
API.post("/data", verifyToken, (req, res) => {
app.database("my-plugin").writeJson(["data.json"], req.body);
res.json(true);
});
server.use("/api/my-plugin", API);
}See the full main.js Reference for all patterns including NestJS integration.
widget.vue
The dashboard widget component. Displayed as a tile on the EA home screen.
- Use PrimeVue components for UI
- Access translations with
$t("namespace.key") - Check permissions with
role("plugin-id", "permission-key")
vue
<template>
<div class="col-12 lg:col-6 xl:col-4">
<!-- your widget UI -->
</div>
</template>
<script>
export default {
// component logic
}
</script>setting.vue
A full-page settings component. Registered under /app/setting/... and linked from the Settings sidebar.
vue
<template>
<div>
<div class="card flex align-items-center justify-content-between">
<h5 class="m-0">{{ $t("myPlugin.name") }}</h5>
<div>
<Button icon="pi pi-save" class="p-button-text"
@click="save()" v-if="role('my-plugin', 'create')" />
<Button icon="pi pi-refresh" class="p-button-text" @click="init()" />
</div>
</div>
<!-- settings content -->
</div>
</template>database/data.json
Defines the initial state of your plugin's data. Stored in database/<plugin-name>/data.json.
json
{
"items": [],
"config": {}
}Development Workflow
bash
# 1. Create plugin
mot create my-plugin --type EA
# 2. Develop — edit script.js, widget.vue, setting.vue
# 3. Run with hot reload
mot run
# 4. Build for production
mot build
# 5. Upload to store
mot uploadRole-Based UI
Use the role() helper in Vue templates to show or hide elements based on permissions:
vue
<Button v-if="role('my-plugin', 'create')" icon="pi pi-plus" />
<Button v-if="role('my-plugin', 'delete')" icon="pi pi-trash" class="p-button-danger" />Administrators configure which roles are granted to which users in the EA admin panel.
i18n Usage
Register translations in script.js then use them in templates:
js
// script.js
this.addLanguage("en", "myPlugin", { title: "My Plugin", save: "Save" });
this.addLanguage("ar", "myPlugin", { title: "الإضافة", save: "حفظ" });vue
<!-- template -->
<h5>{{ $t("myPlugin.title") }}</h5>
<Button :label="$t('myPlugin.save')" />