Appearance
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 dataWhen 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"
}| Field | Description |
|---|---|
versionNumber | Internal version index (start at "0") |
version | Semver version string shown in store |
name | Plugin identifier — must match the folder name |
init | true runs script.js at startup |
type | Always "SA" |
script | Entry script filename |
id | Unique 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-1wrapper 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
axioscall 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
- Create the plugin folder under the SA plugins directory
- Write
setting.jsonandscript.js - Run
mot runto link your plugin folder to the running SA instance - SA picks up changes on reload — no build step needed
- When ready, run
mot buildthen upload via the store