Skip to content

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 -dev core (a development checkout) runs everything.
  • requires.api: the page API version (window.Desorix.apiVersion, currently 1).
  • license: envato for a paid add-on with its own purchase code (Administration → License lists it). Use none otherwise.
  • bundled: true only for modules that ship inside the Desorix release. They are enabled on install and updated with the core.
  • navigation: sidebar entries.
    • can is a workspace policy ability (manageIntegrations, manageAutomation, viewInbox, …).
    • icon is shopping-cart, store or plug. 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; see WoocommerceServiceProvider.
  • 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, add payload.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 renders pages.Index of the shop module.
  • Imports: vue, @inertiajs/vue3, laravel-vue-i18n and @desorix/core come from the core at runtime. @desorix/core is resources/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.css compiles 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 core

Upload 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 ​

ActionWhat happens
Install (zip)Signature and files verified; the folder is placed in modules/; listed as disabled
EnableMigrations run, dist/ is copied to public/modules/{slug}, the boot cache is rebuilt
DisableRoutes, pages and sidebar entries go away; data is kept
Delete dataThe 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