Skip to content

Module Structure

Ray Fung edited this page Feb 16, 2026 · 5 revisions

Module File Structure

Understanding how Razy modules are organized on disk.

Overview

Every Razy module follows a strict directory convention. The framework uses this structure to auto-discover controllers, views, API handlers, web assets, and plugins. Modules live under either a distributor's modules/ folder or the global shared/module/ folder.

Canonical Layout

modules/vendor/my-module/
├── module.php                 # Module metadata (required)
├── default/                   # Default version folder (required)
│   ├── package.php            # Package configuration (required)
│   ├── controller/            # Controller closures
│   │   ├── my_module.php      # Main controller (class name match)
│   │   ├── my_module.index.php   # Route handler "index"
│   │   ├── my_module.detail.php  # Route handler "detail"
│   │   └── api/               # API command handlers
│   │       ├── getData.php    # API command "getData"
│   │       └── submit.php     # API command "submit"
│   ├── view/                  # Template files
│   │   ├── main.tpl           # Main template
│   │   ├── list.tpl           # List template
│   │   └── include/           # Included partials
│   │       └── alert.tpl
│   ├── webasset/              # Web-accessible static files
│   │   ├── css/
│   │   ├── js/
│   │   └── img/
│   └── plugin/                # Plugin closures
│       └── my_plugin.php
└── 1.0.0/                     # Tagged version (optional)
    ├── package.php
    ├── controller/
    └── view/

module.php

The root-level module.php defines module identity. It sits outside any version folder and provides metadata used by the distributor.

return [
    'module_code' => 'vendor/my-module',
    'name'        => 'My Module',
    'author'      => 'Author Name',
    'description' => 'Module description',
    'version'     => '1.0.0',
];

The module_code must be in vendor/package format, matching the directory path.

package.php

Each version folder contains a package.php that defines what gets loaded at runtime:

return [
    'module_code'  => 'vendor/my-module',
    'author'       => 'Author Name',
    'description'  => 'Package description',
    'version'      => '1.0.0',
    'api_name'     => 'mymod',       // Cross-module API identifier
    'require'      => [
        'vendor/other' => '>=1.0.0',
    ],
];

Controller Directory

The controller/ directory contains PHP closure files that the framework auto-loads. Files are named following a strict convention:

File Pattern Purpose Example
{class}.php Main controller — extends Controller, defines lifecycle hooks my_module.php
{class}.{name}.php Route handler closure — registered via addRoute() / addLazyRoute() my_module.index.php
api/{command}.php API command handler — registered via addAPICommand() api/getData.php
demo/{name}.php Custom sub-folders for logical grouping demo/overview.php

Main Controller File

The main controller file returns an anonymous class extending Controller:

return new class extends Controller {
    protected function __onInit(Agent $agent): bool
    {
        $agent->addRoute('/', 'index');
        $agent->addAPICommand('getData', 'api/getData');
        return true;
    }
};

Route Handler Closure

Route handler files return a Closure that receives captured URL parameters:

// controller/my_module.index.php
return function () {
    $template = $this->loadTemplate('main');
    echo $template->output();
};

View Directory

The view/ folder holds .tpl template files used by the Razy Template Engine. Templates are loaded via $this->loadTemplate('name') in the controller, which resolves to view/name.tpl.

You can organize templates into subdirectories. The include/ subfolder is a convention for partial templates referenced by the INCLUDE block type.

Web Assets

The webasset/ directory contains publicly accessible static files (CSS, JS, images). These are served directly by the web server via URL rewriting. Asset paths are managed through the module's package.php asset configuration and the ModuleInfo::getAssets() method.

Plugin Directory

The plugin/ directory contains plugin closure files that extend the Template Engine or Collection system. Plugins are loaded via $this->registerPluginLoader() in the controller. See the Plugin-System page for details.

Version Folders

Modules support multiple versions via named version folders:

modules/vendor/my-module/
├── module.php
├── default/       ← default version
│   ├── package.php
│   └── ...
├── 1.0.0/         ← tagged version
│   ├── package.php
│   └── ...
└── 2.0.0/         ← another tagged version
    ├── package.php
    └── ...

The dist.php configuration selects which version to load:

'modules' => [
    '*' => [
        'vendor/my-module' => '*',       // Latest version
        'vendor/my-module' => 'default', // Default folder
        'vendor/my-module' => '1.0.0',   // Specific tag
    ],
],

See Packaging-Distribution for full version management details.

Shared vs. Distributor Modules

Location Type Description
shared/module/ Shared Available to all distributors. Enabled via global_module or autoload_shared in dist.php
sites/{dist}/modules/ Distributor-specific Only available to that distributor
modules/ Local development Used during development, referenced by dist.php

Real Example: Route Demo

demo_modules/core/route_demo/
├── module.php
└── default/
    ├── package.php
    ├── controller/
    │   ├── route_demo.php              # Main controller
    │   ├── route_demo.main.php         # /route handler
    │   ├── route_demo.article.php      # /article/:id handler
    │   ├── route_demo.product.php      # /product/:slug handler
    │   ├── route_demo.search.php       # /search handler
    │   ├── route_demo.user.php         # /user/:id handler
    │   ├── route_demo.tag.php          # /tag/:name handler
    │   └── route_demo.code.php         # /code/:lang handler
    └── view/
        └── main.tpl

Each .{name}.php file corresponds to a route action registered in the main controller's __onInit via $agent->addRoute().

Clone this wiki locally