Module Structure
A module in Flute CMS is a standalone package that adds new functionality. Each module resides in a separate folder within app/Modules/ and has a standard structure.
File Structure
A typical module looks like this:
- module.json
- BlogProvider.php
What Goes Where
| Directory | Purpose |
|---|---|
Providers/ | Module entry point. Services are registered and resources are loaded here. |
Http/Controllers/ | Controllers handling HTTP requests. Routes are defined via attributes. |
database/Entities/ | Entities for working with the database via Cycle ORM. |
Resources/config/ | Module settings. Available via config('filename.key'). |
Resources/lang/ | Translations for different languages. |
Resources/views/ | Blade templates for displaying pages. |
Components/ | Interactive Yoyo components (work without page reload). |
Widgets/ | Blocks that can be placed on pages via the admin panel. |
Services/ | Module business logic (optional folder). |
Middleware/ | Request filters (permission checks, validation, etc.). |
Not all folders are mandatory. Minimally, only module.json and Providers/ are required.
Module Manifest (module.json)
Every module must have a module.json file in the root. This is the module’s “passport” — without it, the system won’t know about the module.
Minimal Example
{
"name": "Blog",
"version": "1.0.0",
"description": "Blog module for publishing articles",
"providers": [
"Flute\\Modules\\Blog\\Providers\\BlogProvider"
]
}Full Example with Dependencies
If your module requires a specific PHP version, other modules, or composer packages:
{
"name": "Shop",
"version": "2.0.0",
"description": "Online store with cart and checkout",
"authors": ["Ivan Petrov"],
"providers": [
"Flute\\Modules\\Shop\\Providers\\ShopProvider"
],
"dependencies": {
"php": ">=8.2",
"flute": ">=1.0.0",
"modules": {
"Payments": ">=1.0.0"
},
"extensions": ["curl", "gd"],
"composer": {
"guzzlehttp/guzzle": "^7.0"
}
}
}Dependency Options
| Type | Description | Example |
|---|---|---|
php | Minimum PHP version | ">=8.2" |
flute | Minimum Flute CMS version | ">=1.0.0" |
modules | Other modules that must be active | {"Payments": ">=1.0.0"} |
extensions | PHP extensions | ["curl", "gd"] |
composer | Composer packages | {"guzzlehttp/guzzle": "^7.0"} |
theme | Required theme | {"standard": ">=1.0.0"} |
If dependencies are not met, the module is automatically disabled. Check logs if the module isn’t working.
Module Provider
The provider is the main class of the module. It tells the system which services to register and which resources to load.
Basic Provider
<?php
namespace Flute\Modules\Blog\Providers;
use Flute\Core\Support\ModuleServiceProvider;
class BlogProvider extends ModuleServiceProvider
{
/**
* Module name (must match folder name)
*/
protected ?string $moduleName = 'Blog';
/**
* Service registration.
* Called ALWAYS, even if the module is disabled.
* Register classes in the DI container here.
*/
public function register(\DI\Container $container): void
{
// Register service for working with articles
$container->set(ArticleService::class, \DI\autowire());
}
/**
* Module initialization.
* Called ONLY for active modules.
* Load resources and configure the module here.
*/
public function boot(\DI\Container $container): void
{
// Load all resources with one command
$this->bootstrapModule();
}
}What bootstrapModule() Does
The bootstrapModule() method is “magic” that automatically finds and loads all module resources:
Database Entities
Looks for classes in database/Entities/ and registers them in Cycle ORM. After that, you can work with the database via rep(Article::class)->findAll().
Configuration
Files from Resources/config/ become available via config(). For example, blog.php is available as config('blog.posts_per_page').
Translations
Files from Resources/lang/ru/messages.php are registered for localization. Use __('messages.welcome') in code.
Controllers with Routes
Scans folders Http/Controllers/, Controllers/, and Submodules/*/Controllers/. Routes are taken from #[Get], #[Post] attributes, etc.
Yoyo Components
Classes from Components/ are automatically registered. Component ArticleCard.php becomes available as @yoyo('article-card').
Widgets
Classes from Widgets/ implementing WidgetInterface can be used on pages.
If you need a routes.php file for manual route definition, add to boot():
$this->loadRoutesFrom('routes.php');Manual Resource Loading
Sometimes you need more precise control. Instead of bootstrapModule(), you can load resources individually:
public function boot(\DI\Container $container): void
{
// Only DB entities
$this->loadEntities();
// Only configuration
$this->loadConfigs();
// Only translations
$this->loadTranslations();
// Views with custom namespace
// Now you can use: view('myblog::pages.index')
$this->loadViews('Resources/views', 'myblog');
// Routes from file
$this->loadRoutesFrom('routes.php');
// SCSS styles (compiled automatically)
$this->loadScss('Resources/assets/scss/blog.scss');
// Components and widgets
$this->loadComponents();
$this->loadWidgets();
}Module Lifecycle
When Flute starts, modules go through several stages:
Module Discovery
The system scans the app/Modules/ folder and finds all module.json files.
Status Check
Each module is checked against the database. Statuses:
active— module is workingdisabled— module is disabled by administratornotinstalled— module found but not yet installed
Dependency Check
For each active module, dependencies from module.json are checked. If something is wrong (missing PHP extension, missing dependent module), the module is automatically disabled.
Registration (register)
The register() method is called for ALL modules. Services are registered in the DI container here.
Initialization (boot)
The boot() method is called only for ACTIVE modules. Resources are loaded and setup is performed here.
Caching
For performance, Flute caches file scanning results. By default, the cache lives for 1 hour.
When to Clear Cache
- Added a new controller or component
- Changed module folder structure
- Added new translations
How to Clear Cache
// Clear cache of all modules (module list, statuses)
app(\Flute\Core\ModulesManager\ModuleManager::class)->clearCache();
// Clear cache of a specific module (files, translations, components)
$provider->clearFileCache();Change Cache Duration
public function boot(\DI\Container $container): void
{
// Cache for 2 hours instead of 1
$this->setCacheDuration(7200);
$this->bootstrapModule();
}Module Extensions
If a module is large, it can be split into extensions:
class ShopProvider extends ModuleServiceProvider
{
/**
* Extensions are loaded automatically
*/
public array $extensions = [
CartExtension::class,
PaymentExtension::class,
DeliveryExtension::class,
];
}Each extension is a class implementing ModuleExtensionInterface:
<?php
namespace Flute\Modules\Shop\Extensions;
use Flute\Core\ModulesManager\Contracts\ModuleExtensionInterface;
class CartExtension implements ModuleExtensionInterface
{
public function register(): void
{
// Cart services registration
}
public function boot(): void
{
// Cart initialization
}
}For extensions, only register() is called automatically. boot() is NOT called automatically.
Naming Conventions
To ensure modules are compatible and code is readable, follow these rules:
| What | How to Name | Example |
|---|---|---|
| Module Folder | StudlyCase | Blog, UserProfile, PaymentGateway |
| Classes | StudlyCase | ArticleController, PaymentService |
| Methods | camelCase | getArticles(), processPayment() |
| Template Files | kebab-case | article-card.blade.php, user-profile.blade.php |
| Constants | UPPER_CASE | MAX_ARTICLES, DEFAULT_LIMIT |
| DB Tables | snake_case with prefix | blog_articles, shop_orders |
The module folder name must match the name field in module.json and the $moduleName property in the provider.