Skip to content
Bondry

Module anatomy and module.json

The folder layout, every field of the manifest, and the lifecycle methods the kernel calls.

A module is a self-contained directory, loadable at runtime and installable from a zip. The factory modules use exactly this contract.

Folder layout#

Code
modules/directory/
  module.json                 manifest (required)
  src/
    DirectoryServiceProvider.php
    Models/
    Http/Controllers/
    Http/Requests/
    Support/
  database/
    migrations/               the module tables
    seeders/
  resources/
    views/                    Blade, overridable by a theme
    lang/                     the module translations
    assets/                   css and js, ALREADY COMPILED
  routes/
    web.php
    admin.php
  config/directory.php

The manifest#

JSON
{
  "name": "Directory",
  "slug": "directory",
  "version": "1.2.0",
  "description": "A configurable catalogue.",
  "author": { "name": "You", "url": "https://example.com" },
  "requires": {
    "bondry": ">=1.0 <2.0",
    "php": ">=8.2",
    "extensions": ["gd"],
    "modules": { "payments": ">=1.0" }
  },
  "provider": "Modules\\Directory\\DirectoryServiceProvider",
  "provides": {
    "permissions": ["directory.view", "directory.manage"],
    "hooks": ["profile.tabs", "admin.menu", "search.sources"]
  },
  "settings": "admin.directory.settings",
  "assets": { "css": ["assets/directory.css"], "js": ["assets/directory.js"] },
  "parent": null,
  "tested_up_to": "1.4.0",
  "official": false,
  "licensing": { "product_slug": "your-directory" }
}
Field Meaning
slug The identity. [a-z0-9-], unique, and it never changes between versions.
version Semver. It is what drives onUpdate.
requires Semver ranges. The kernel refuses to install when they are not met.
provider The root class. It has to exist and extend the base provider.
provides.permissions Registered into the group matrix on install.
settings A route name. The module list links to it when the module is enabled.
parent The slug of the module this one extends. Absent means it extends the core.
tested_up_to The newest core version you tested against.
official Only accepted for packages signed by us.
licensing.product_slug Present only on a paid module. See License API.

The service provider#

Your provider extends Bondry\Kernel\Modules\ModuleServiceProvider and implements the lifecycle:

PHP
interface ModuleContract
{
    // Every request, only while the module is enabled:
    public function register(): void;   // container bindings
    public function boot(): void;       // routes, views, migrations, translations, hooks

    // Called by the runtime installer, not on every request:
    public function onInstall(): void;
    public function onUninstall(bool $purge): void;
    public function onEnable(): void;
    public function onDisable(): void;
    public function onUpdate(string $from, string $to): void;
}

boot() is where you call loadRoutesFrom, loadViewsFrom with your namespace, loadMigrationsFrom, loadTranslationsFrom and register your hooks.

register() runs before anything is resolved: bindings only, no database.

Warning boot() runs on every request. Anything expensive there is a cost your buyers pay on every page view, on shared hosting.

Views and the theme#

Register your views with a namespace:

PHP
$this->loadViewsFrom(__DIR__.'/../resources/views', 'directory');

Rendering directory::item.show then resolves through the theme chain first, so a buyer can override any screen of your module from their theme without editing your files. See Themes.

Install flow#

What the panel does with your zip, in order:

  1. opens it in an isolated temporary directory;
  2. reads module.json and validates the schema;
  3. checks requires and any slug or version conflict;
  4. verifies the signature when official is set;
  5. moves it to modules/<slug>/ and runs onInstall(), or onUpdate() when a version was already there;
  6. publishes assets to public/modules/<slug>/;
  7. records it in the modules table;
  8. clears the route, view and config caches.