Appearance
Building a module
Modules add features to Desorix without changing its code: their own tables, routes, pages, translations and sidebar entries. They can be installed, enabled, disabled and updated from the browser. WooCommerce (modules/Woocommerce) is the reference module; copy its layout.
Decisions: D-115 (module system), D-116 (frontend), D-117 (WooCommerce).
Layout
modules/Shop/
module.json
src/ PSR-4: Modules\Shop\ → src/
ShopServiceProvider.php
Models/ Http/Controllers/ …
database/migrations/ prefix your tables (shop_…)
routes/web.php inside the workspace group: /{workspace}/…
routes/webhooks.php under /webhooks/…, stateless
lang/en.json, ar.json generated and checked by npm run lang:sync
resources/js/module.ts registers the pages
resources/js/module.css Tailwind for your pages
resources/js/pages/… Vue pages
dist/ built by npm run build:modules (module.js, module.css)module.json
json
{
"slug": "shop",
"name": "Shop",
"description": "One line shown under Administration → Modules.",
"version": "1.0.0",
"namespace": "Modules\\Shop\\",
"provider": "Modules\\Shop\\ShopServiceProvider",
"requires": { "desorix": "1.0.0", "php": "8.3.0", "api": 1 },
"license": "envato",
"envato_item_id": 12345678,
"bundled": false,
"navigation": [
{ "label": "Shop", "route": "shop.index", "icon": "store", "can": "manageIntegrations" }
]
}requires.desorix: the oldest core the module works with. A-devcore (a development checkout) runs everything.requires.api: the page API version (window.Desorix.apiVersion, currently 1).license:envatofor a paid add-on with its own purchase code (Administration → License lists it). Usenoneotherwise.bundled:trueonly for modules that ship inside the Desorix release. They are enabled on install and updated with the core.navigation: sidebar entries.canis a workspace policy ability (manageIntegrations,manageAutomation,viewInbox, …).iconisshopping-cart,storeorplug. Anything else shows a puzzle piece.
Backend
The provider extends App\Modules\ModuleServiceProvider. It loads migrations, JSON translations, routes/web.php (in the same workspace group as the core pages: slug, membership, auth, verified) and routes/webhooks.php.
- Models: tenant models use
App\Support\Tenancy\BelongsToWorkspace. Bind route parameters to the workspace in the URL; seeWoocommerceServiceProvider. - Sending WhatsApp messages: always use
App\WhatsApp\Messaging\MessageSender. It records category, country and cost (a hard rule). To show where a message came from in the inbox, addpayload.desorix.origin = ['text' => __('Shop order :number', [], 'en'), 'params' => [...]]. - Dependencies: modules have no
vendor/in v1. Use only packages the core already requires. - Migrations only add within 1.x of your module, as in the core (D-112).
Pages
Module pages are compiled into one IIFE that uses the core's copy of Vue, Inertia, i18n and the UI components:
ts
// resources/js/module.ts
import './module.css';
import Index from './pages/Index.vue';
const lang = import.meta.glob<Record<string, string>>('../../lang/*.json', { eager: true, import: 'default' });
window.Desorix?.registerModule('shop', {
apiVersion: 1,
pages: { Index },
lang: Object.fromEntries(Object.entries(lang).map(([path, messages]) => [path.replace(/^.*\/(.+)\.json$/, '$1'), messages])),
});- Rendering a page:
Inertia::render('shop::Index', [...])in a controller renderspages.Indexof theshopmodule. - Imports:
vue,@inertiajs/vue3,laravel-vue-i18nand@desorix/corecome from the core at runtime.@desorix/coreisresources/js/modules/api.ts: Button, Input, Label, Card, Dialog, Select, Badge, Heading, InputError, CopyField, date formatting,useWorkspaceSlug, … Other npm packages (icons from@lucide/vue, for example) are bundled into your module. - URLs: pass every URL as a prop from the controller (
route(...)). Wayfinder helpers aren't available to modules. - Styles:
module.csscompiles Tailwind for your files only, with the core's theme. At build time every selector is prefixed with.dx-module-{slug}, and your pages are wrapped in that class. That way your utilities can't override the core's. Content that is teleported out of the page (dialogs, dropdowns) is outside that scope: use core components there.
Build with npm run build:modules (included in npm run build). In development, node scripts/build-modules.mjs --watch.
Translations
npm run lang:sync writes modules/Shop/lang/en.json from your PHP and Vue code and the navigation labels, and lists missing keys for each language file in lang/. CI fails if a translation is missing.
Releasing
bash
make module-release MODULE=Shop # → dist/shop-1.0.0.zip, signed like the coreUpload the zip to the license server as a release of product shop. Buyers install it under Administration → Updates → Update from a zip → Install a new module, then enable it under Administration → Modules.
Lifecycle
| Action | What happens |
|---|---|
| Install (zip) | Signature and files verified; the folder is placed in modules/; listed as disabled |
| Enable | Migrations run, dist/ is copied to public/modules/{slug}, the boot cache is rebuilt |
| Disable | Routes, pages and sidebar entries go away; data is kept |
| Delete data | The module's migrations are rolled back (its tables dropped), and its files are deleted unless bundled. The admin must type the module name. |
| Update (zip or one-click) | The module folder is swapped, then migrations run and pages are republished |