Skip to content

SA Development Guide ​

Prerequisites ​

  • Node.js 18+
  • Motanamy CLI (npm install -g motanamy-cli)
  • A running Motanamy SA instance for testing

File Structure ​

Every SA plugin follows this structure:

my-plugin/
├── 1.0.0/                  ← version folder
│   ├── setting.json        ← plugin manifest
│   ├── script.js           ← registration script (runs on load)
│   ├── widget.vue          ← dashboard widget (optional)
│   ├── setting.vue         ← settings page (optional)
│   ├── my-page.vue         ← additional pages (optional)
│   ├── README-AR.md        ← Arabic documentation
│   ├── README-EN.md        ← English documentation
│   └── images/             ← plugin images/icons
└── database/
    └── my-plugin/
        └── data.json       ← initial data

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 ​

json
{
    "versionNumber": "0",
    "version": "1.0.0",
    "name": "my-plugin",
    "init": true,
    "type": "SA",
    "script": "script.js",
    "id": "_uniqueId"
}
FieldDescription
versionNumberInternal version index (start at "0")
versionSemver version string shown in store
namePlugin identifier — must match the folder name
inittrue runs script.js at startup
typeAlways "SA"
scriptEntry script filename
idUnique store ID (assigned on upload)

script.js ​

script.js runs once when SA starts. Register your plugin by assigning a function to plugins["name"]. Inside the function, this exposes all registration methods.

js
plugins["my-plugin"] = function () {
    // 1. Register translations
    this.addLanguage("ar", "my-plugin", { name: "البرنامج" });
    this.addLanguage("en", "my-plugin", { name: "My Plugin" });

    // 2. Add to sidebar nav
    this.addTo({
        label: "My Plugin",
        key: "my-plugin.name",
        icon: "mdi mdi-puzzle-outline",
        to: "/app/my-plugin"
    }, "setting");

    // 3. Register the page route
    this.addPage({
        name: "/[my-plugin]index.vue",
        path: "my-plugin",
        file: ["my-plugin", "index.vue"]
    }, "setting");

    // 4. Register a dashboard widget
    this.addWidget({
        name: "/[my-plugin]widget.vue",
        id: "init-my-plugin",
        file: ["my-plugin", "widget.vue"]
    });
}

See the script.js API Reference for all available methods.

widget.vue ​

Widgets are displayed on the SA dashboard. Always wrap the root element in the responsive grid column so widgets align with the SA layout:

vue
<template>
    <div class="col-12 lg:col-6 xl:col-3 p-1">
        <div class="card h-full">
            <h5>{{ $t("my-plugin.name") }}</h5>
            <p>{{ value }}</p>
        </div>
    </div>
</template>

<script>
import api from 'api';

export default {
    data() {
        return { value: null }
    },
    created() {
        this.value = api.database("my-plugin").readJson(["data.json"]);
    }
}
</script>

The col-12 lg:col-6 xl:col-3 p-1 wrapper is required — it places the widget in the dashboard grid.

Pages ​

Regular and settings pages are plain Vue components. Use the standard card layout:

vue
<template>
    <div>
        <div class="card flex align-items-center justify-content-between">
            <h5 class="m-0">{{ $t("my-plugin.name") }}</h5>
            <Button icon="pi pi-save" class="p-button-text" @click="save()" />
        </div>
        <div class="card">
            <!-- content -->
        </div>
    </div>
</template>

Database ​

SA plugins use the api module to read and write local JSON files from the database/ folder:

js
import api from 'api';

// Read
const data = api.database("my-plugin").readJson(["data.json"]);

// Write
api.database("my-plugin").writeJson(["data.json"], data);

// Nested path
const item = api.database("my-plugin").readJson(["folder", "items.json"]);

The database() argument is the plugin name — it matches the folder under database/my-plugin/.

Unlike EA, there is no axios call or HTTP server. All reads/writes are synchronous local operations.

Icon Picker ​

SA provides a built-in icon picker via EventBus:

js
import { EventBus } from 'global';

EventBus.emit("openIcons", (selectedIcon) => {
    this.form.icon = selectedIcon; // e.g. "mdi mdi-home"
});

Use this to let users pick an icon for any field instead of typing it manually.

i18n Usage ​

Translations registered via addLanguage() are available in templates with $t():

vue
<template>
    <div>
        <h5>{{ $t("my-plugin.name") }}</h5>
        <Button :label="$t('button.save')" />
    </div>
</template>

Development Workflow ​

bash
mot create my-plugin --type SA
mot link add      # once: point the CLI at your installed SA app
cd my-plugin
mot dev           # run the plugin inside SA
mot publish       # publish to the store (see Publish to Store)

Resources ​