Skip to content

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 state

When 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"
}
FieldTypeDescription
versionNumberstringInternal version counter, increments on each update
versionstringSemantic version string
namestringPlugin identifier (used in file paths)
initbooleanWhether the plugin initializes on EA startup
type"EA"Platform type
scriptstringFrontend entry point filename
mainstringBackend entry point filename (optional, e.g. "main.js")
assetsstringAssets folder name (optional, e.g. "assets")
idstringUnique 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 upload

Role-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')" />

Resources