Plugin Manifest Reference (module.json)

A plugin is described by two files at the module root: module.json (read by nwidart and by the Plugin Manager) and composer.json (read by Composer's merge plugin and checked on upload).

module.json

Modules/Stripe/module.json (a real, minimal manifest)

{
    "name": "Stripe",
    "alias": "stripe",
    "description": "Adds Stripe payment processing to TruPayments ...",
    "version": "1.0.0",
    "author": "DotArtisan",
    "keywords": ["payments", "stripe", "gateway", "trupayments"],
    "priority": 1003,
    "requires": { "trupayments": "^1.0" },
    "providers": ["Modules\\Stripe\\Providers\\StripeServiceProvider"]
}

Required keys

KeyTypeRule (enforced by PluginMetadataReader)Error key
namestringStudlyCase, ^[A-Z][A-Za-z0-9]{1,63}$. Must equal the folder name and the namespace segment Modules\{name}\name_invalid
aliasstringLower-case kebab, ^[a-z][a-z0-9-]{1,63}$. Used as the config key, view namespace, translation namespace and in other plugins' requiresalias_invalid
descriptionstringNon-empty; shown in the Plugin Managerdescription_missing
versionstringAnything Composer's VersionParser::normalize() accepts (1.0.0, v2.1, 1.0.0-beta1). Compared against other plugins' requires constraintsversion_invalid
providerslist<string>At least one fully-qualified class, each matching ^Modules\\{Name}\\[A-Za-z0-9_\\]+$; the file app/{path}.php must exist in the archiveproviders_missing, providers_invalid, provider_invalid, provider_missing

Optional keys the platform understands

KeyTypeMeaning
requiresmap<alias, constraint> or list<alias>Plugins that must be installed and active, with Composer constraints ("trupayments": "^1.0"). A list means any version. Aliases must match the alias rule; constraints must parse. The legacy key requirements (list) is merged in. There is no key for the TruCMS core version
public_pathslist<string>First URL segments your public routes own (["booking"]). They are added to the reserved-slug list so a CMS page can never be created at /booking, and removed from the catch-all route's pattern. Each must match ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$. The lower-case module name is always reserved too
protectedbooltrue forbids deleting the plugin from the UI (non-boolean values are rejected: protected_invalid)
authorstring or {name, url}Displayed in the manager
author_url (alias website), support_url (alias support)http(s) URLLinks in the manager; other schemes are rejected (url_invalid)
paymentsbooltrue marks a payment consumer; Module::paymentModules() / withPayment() list them (Booking, TruCommerce)
priorityintnwidart sorts modules by it (ascending) before calling register() and boot(). Bundled values: TruCommerce 99, TruPayments 100, Booking 1000, gateways 1002–1004. Use a high number so you load after the modules you extend
keywordslist<string>Informational
files, aliasesnwidart keysfiles are required early by nwidart (registerFiles()); aliases registers class aliases

Unknown keys are ignored, so you can store your own metadata, and they are readable with Module::find('Name')->get('key').

composer.json

Modules/Booking/composer.json (trimmed)

{
    "name": "dotartisan/booking",
    "require": { "php": "^8.3", "astrotomic/laravel-translatable": "^11.16" },
    "autoload": {
        "files": ["booking.php"],
        "psr-4": {
            "Modules\\Booking\\": "app/",
            "Modules\\Booking\\Database\\Factories\\": "database/factories/",
            "Modules\\Booking\\Database\\Seeders\\": "database/seeders/"
        }
    },
    "autoload-dev": { "psr-4": { "Modules\\Booking\\Tests\\": "tests/" } }
}

Directory conventions (nwidart generator paths)

config/modules.php defines where nwidart expects files. The ones that matter: providers and route provider in app/Providers, controllers app/Http/Controllers, models app/Models, config config (every PHP file is merged: config.php → config('alias')), migrations database/migrations, seeders database/seeders, translations lang, views resources/views, assets resources/assets (published to public/modules/{lowername} by php artisan module:publish Name), routes routes, tests tests/Feature and tests/Unit.

Reading the manifest at runtime

use Nwidart\Modules\Facades\Module;

Module::isEnabled('Testimonials');                    // bool, reads modules_statuses.json
Module::find('Testimonials')?->get('version');        // any manifest key
Module::allEnabled();                                 // enabled modules
Module::requirements('booking');                      // ['trupayments'] (macro added by TruCMS)
Module::requiredBy('trupayments');                    // collection of modules that require it
module_path('Testimonials', 'resources/views');       // helper: absolute path inside a module

Common manifest errors and fixes

Message shownFix
The package must contain exactly one module.json file at the module rootPut module.json in the top folder of the ZIP (or the ZIP root) and ensure no nested copies exist.
The declared provider … does not match the module namespaceProviders must start with Modules\{Name}\; the folder/alias must agree with name.
The declared provider file for … is missingThe class path maps to app/Providers/YourServiceProvider.php (everything after Modules\{Name}\ is a path inside app/).
composer.json must map … to the module app directoryAdd "Modules\\{Name}\\": "app/" to autoload.psr-4.
Plugin dependencies must be a map of plugin aliases to semantic-version constraintsUse {"trupayments": "^1.0"}, not a string.
… is already installed. Plugin updates are not supported by this upload flowSee Package, install and update.
The package must contain a single module directoryDo not zip several folders (macOS __MACOSX folders count).