PHPackages                             synerdy/modsx - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. synerdy/modsx

ActiveLibrary

synerdy/modsx
=============

Convention-based modules for Laravel: organise, back up, version and restore parts of your application using a directory-name prefix.

v0.2.4(today)024↑2775%MITPHPPHP ^8.3

Since Aug 24Pushed todayCompare

[ Source](https://github.com/Synerdy/modsx)[ Packagist](https://packagist.org/packages/synerdy/modsx)[ Docs](https://github.com/Synerdy/modsx)[ RSS](/packages/synerdy-modsx/feed)WikiDiscussions master Synced today

READMEChangelog (5)Dependencies (10)Versions (6)Used By (0)

Modsx — convention-based modules for Laravel
============================================

[](#modsx--convention-based-modules-for-laravel)

[![Latest Version](https://camo.githubusercontent.com/d9ec11d4ce6b54e0e26e52e1635b4c2a9960b94ec8f25bb6d888a23799f9c430/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73796e657264792f6d6f6473782e737667)](https://packagist.org/packages/synerdy/modsx)[![Tests](https://github.com/Synerdy/modsx/actions/workflows/tests.yml/badge.svg)](https://github.com/Synerdy/modsx/actions)[![License](https://camo.githubusercontent.com/699cc1778d792544fe7082725415ddc8ce9e4c7cfad2310fbed3a52e23283c88/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f73796e657264792f6d6f6473782e737667)](LICENSE)

Organise a Laravel application into modules using nothing but a directory-naming convention — then back them up, version them and restore them from the command line.

**Read this in another language:** [Polski](README.pl.md)

---

The idea
--------

[](#the-idea)

Most Laravel module packages ask you to restructure your application: a separate source tree, a service provider per module, custom autoloading, their own routing and view namespaces. That is a lot of machinery to adopt, and a lot to unwind if you change your mind.

Modsx takes the opposite approach. **A module is just a set of directories that share a name.** You create them yourself, in the places Laravel already puts things:

```
resources/views/modsx-blog/
app/Http/Controllers/ModsxBlog/

```

That is a module. It works immediately — Laravel resolves those views and controllers exactly as it always has, because nothing about the framework has changed. No provider, no namespace registration, no autoload rules.

This package doesn't create that structure and plays no part in running it. It only **finds** it and manages it: backup, versioning, restore, removal.

Three things follow from this:

- **You can adopt the convention without installing anything.** Start prefixing directories today; install the package the day you actually want backups.
- **You can uninstall it and lose nothing.** Remove the package and your modules keep working — they were never anything but ordinary Laravel directories.
- **It composes with the rest of the ecosystem.** Livewire, Filament, Inertia, Folio — anything that reads from `app/`, `resources/` or `routes/` sees ordinary directories, because that is what they are.

The trade-off is honest: this is not a package manager. It does not resolve dependencies between modules, does not manage Composer requirements, and does not touch your database. See [Limitations](#limitations).

---

Requirements
------------

[](#requirements)

PHP8.3+Laravel12.x, 13.x---

Installation
------------

[](#installation)

```
composer require synerdy/modsx
```

The service provider is auto-discovered. To change anything, publish the config:

```
php artisan vendor:publish --tag=modsx-config
```

Backups are written to `modsx-backups/` in your project root. You almost certainly want them out of version control:

```
# .gitignore
/modsx-backups
```

Committing them instead is a legitimate choice if you want module versions to travel with the repository — just be aware that a backup is a full directory copy, so the repo will grow with every one.

---

Naming convention
-----------------

[](#naming-convention)

**This is the one section worth reading carefully.** Everything else follows from it.

A module has a single canonical name in **StudlyCase**:

```
Blog        UserProfile        AdminPanel

```

Two directory forms are derived from that name, and Modsx matches **both**:

WhereForm`Blog``UserProfile`Directories under `resources/`, `public/`, `lang/``modsx-` + kebab-case`modsx-blog``modsx-user-profile`PHP namespace directories under `app/`, `database/``Modsx` + StudlyCase`ModsxBlog``ModsxUserProfile`This is Laravel's own convention, not an invention of this package — the framework maps `App\View\Components\UserProfile` to `` using exactly the same StudlyCase ↔ kebab-case conversion. If your directories already follow Laravel's naming, they already follow this one.

> **Both forms must come from the same name.**
>
> `modsx-userprofile` and `ModsxUserProfile` are **two different modules**: the first is `Userprofile`, the second is `UserProfile`. Back up `UserProfile` and the `modsx-userprofile` views are silently left behind.
>
> Write the name in StudlyCase first, then convert: `UserProfile` → `user-profile`, never `userprofile`. If you suspect you've already made this mistake somewhere, `php artisan modsx:doctor` will find it.

The `modsx` prefix itself is configurable, so if you prefer `mod-` or your company's initials, change it once and the same rules apply.

### Example layout

[](#example-layout)

```
app/
├── Http/Controllers/ModsxBlog/
│   ├── PostController.php
│   └── CategoryController.php
├── Livewire/ModsxBlog/
│   └── PostList.php
├── Models/ModsxBlog/
│   └── Post.php
└── Services/ModsxBlog/
    └── PostPublisher.php

resources/
├── views/modsx-blog/
│   ├── index.blade.php
│   └── show.blade.php
├── views/components/modsx-blog/
│   └── post-card.blade.php
├── css/modsx-blog/
│   └── blog.css
└── js/modsx-blog/
    └── editor.js

```

None of these directories is mandatory. A module can be a single view folder.

### Livewire

[](#livewire)

Livewire 3 and 4 work without special handling, because Livewire discovers components by directory:

```
app/Livewire/ModsxBlog/PostList.php               →
resources/views/livewire/modsx-blog/post-list.blade.php

```

Livewire 4 single-file components live under `resources/views/components/`, so the same prefix applies:

```
resources/views/components/modsx-blog/post-list.blade.php

```

There is nothing Livewire-specific in this package — it sees ordinary directories, which is precisely why it keeps working across Livewire versions.

---

Commands
--------

[](#commands)

Run any command without arguments and it will prompt you, with a picker for existing names rather than free-text entry.

CommandPurpose`modsx:list`Modules currently present in the application`modsx:path {name?}`Directories belonging to a module`modsx:backup {name?}`Copy a module to a new numbered version`modsx:backuplist {name?}`Available backup versions`modsx:delete {name?}`Back up, then remove the module`modsx:restore {name?} {version?}`Back up the current state, then restore a version`modsx:diff {name?} {version?}`Compare current state against a backup version`modsx:info {name?}`Show size, file count, and backup history`modsx:prune {name?}`Remove old versions, keeping the newest`modsx:doctor`Check for naming problems and orphaned backups### `modsx:list`

[](#modsxlist)

```
php artisan modsx:list
php artisan modsx:list --json
```

```
 Module        Directories   Backups   Latest
 Blog          4             3         0003
 UserProfile   2             -         -

```

A module appears here if **any** of its directories exists.

### `modsx:path`

[](#modsxpath)

Shows exactly which directories Modsx considers part of a module — that is, exactly what a backup would copy. Worth running before your first `modsx:delete`.

```
php artisan modsx:path Blog
php artisan modsx:path            # every module
php artisan modsx:path --json
```

### `modsx:backup`

[](#modsxbackup)

Copies every directory belonging to the module into a new sequential version.

```
php artisan modsx:backup Blog
```

```
modsx-backups/
└── Blog/
    ├── 0001/
    │   ├── modsx.json
    │   ├── app/Http/Controllers/ModsxBlog/
    │   └── resources/views/modsx-blog/
    └── 0002/
        └── ...

```

Version numbers come from the highest existing number, never from whatever the filesystem lists last, and the command refuses to write to a path that already exists. Versions are never overwritten and never reused.

Each version carries a `modsx.json` manifest recording the module name, creation time, the exact source paths, and the PHP, Laravel and package versions in use. Restore reads it, so it puts directories back where they came from rather than inferring their location.

The whole copy is assembled in a staging directory and moved into place at the end, so an interrupted backup leaves no half-written version behind.

### `modsx:backuplist`

[](#modsxbackuplist)

```
php artisan modsx:backuplist                    # every module
php artisan modsx:backuplist Blog
php artisan modsx:backuplist Blog --limit=5     # newest 5
php artisan modsx:backuplist --json
```

```
 Blog
 Version   Created                     Directories
 0001      2026-08-20T09:14:02+02:00   2
 0002      2026-08-21T17:40:55+02:00   2

```

### `modsx:delete`

[](#modsxdelete)

**Backs up first**, and removes nothing unless the backup succeeded.

```
php artisan modsx:delete Blog
php artisan modsx:delete Blog --force          # skip the prompt, for CI
php artisan modsx:delete Blog --skip-backup    # if you really mean it
```

The directories that will be removed are listed before the confirmation prompt, and the version number created by the backup is printed, so you always know what to pass to `modsx:restore`.

### `modsx:restore`

[](#modsxrestore)

```
php artisan modsx:restore Blog          # newest version
php artisan modsx:restore Blog 0003     # a specific version
php artisan modsx:restore               # interactive
```

The sequence is:

1. Back up the module's current state, so the restore is itself reversible.
2. Remove the current directories.
3. Copy the chosen version back into the application.

Everything is copied out of the backup **before** the application is touched, so a corrupt or incomplete backup is discovered while the current state is still intact.

If the module isn't currently in the application, steps 1 and 2 are skipped and this becomes an **install from backup** — which is how you move a module between projects: copy `modsx-backups/Blog/` across and restore it.

### `modsx:prune`

[](#modsxprune)

```
php artisan modsx:prune                          # every module, config default
php artisan modsx:prune Blog --keep=5
php artisan modsx:prune --keep=3 --dry-run       # show the plan, change nothing
php artisan modsx:prune --dry-run --json         # machine-readable plan, for CI
```

Lists exactly which versions would go, then asks. The newest version is never removed, whatever `--keep` is set to.

### `modsx:diff`

[](#modsxdiff)

```
php artisan modsx:diff Blog          # against the newest version
php artisan modsx:diff Blog 0003     # against a specific version
php artisan modsx:diff               # interactive
php artisan modsx:diff Blog --json
```

Compares the module in your application against a backup version, **file by file**, using a content hash:

- **Added** — in the application now, not in that version. A restore would delete these.
- **Modified** — in both, but the contents differ. A restore would overwrite these.
- **Removed** — in that version, gone from the application. A restore would bring these back.
- **Unchanged** — identical on both sides.

The comparison is on file contents, not directory names, so a module whose files were all rewritten in place is reported as modified rather than unchanged.

```
php artisan modsx:diff Blog --summary   # counts only, no file list
```

Worth running before `modsx:restore`: it tells you exactly what you are about to lose.

### `modsx:info`

[](#modsxinfo)

```
php artisan modsx:info Blog
php artisan modsx:info --json
```

Shows:

- **Current state**: whether the module exists in the application, how many directories and files it has, and its total size on disk
- **Backup history**: number of backed-up versions, total backup size, and a table of each version with its creation date and size

Useful for understanding storage usage and deciding whether to prune old versions.

### `modsx:doctor`

[](#modsxdoctor)

```
php artisan modsx:doctor
php artisan modsx:doctor --json    # exit code 1 if problems were found, for CI
```

Reports:

- **Modules whose names differ only in word boundaries**, such as `Userprofile` alongside `UserProfile`. Both are valid names, so nothing else flags this — but it is almost always one module that was meant to be one module, and it will be backed up as two.
- Modules present in only one of the two directory forms (informational).
- Backups with no matching module in the application (informational).

---

Configuration
-------------

[](#configuration)

`config/modsx.php`:

```
return [

    // Directory prefix. 'modsx' matches modsx-blog and ModsxBlog.
    'prefix' => env('MODSX_PREFIX', 'modsx'),

    // Where versioned backups are written.
    'backup_path' => env('MODSX_BACKUP_PATH', base_path('modsx-backups')),

    // Only these paths are scanned. Keeping the list tight is what keeps
    // discovery fast: a full scan of the project root would walk storage/,
    // .git/ and public/build/.
    'scan_paths' => [
        'app', 'config', 'database', 'lang', 'public', 'resources', 'routes', 'tests',
    ],

    // Directory names never descended into.
    'exclude' => [
        'vendor', 'node_modules', 'storage', 'bootstrap/cache', '.git', '.idea', '.vscode',
    ],

    // 4 gives 0001, 0002, ...
    'version_padding' => 4,

    // Default for modsx:prune.
    'prune' => ['keep' => 5],

];
```

Two notes:

- If you change `prefix` after creating modules, rename the existing directories to match. Nothing is found under the old prefix.
- The backup directory is never scanned for modules, wherever you point it — including inside a path that is otherwise scanned.

---

Limitations
-----------

[](#limitations)

Deliberate, and worth knowing before you rely on this:

- **Directories only.** Single files belonging to a module — `routes/modsx-blog.php`, `config/modsx-blog.php`, migration files, `lang/en/modsx-blog.php` — are **not** backed up, deleted or restored. Keep module code in directories, or handle those files yourself.
- **No database.** Restoring an older version does not roll back migrations or touch data. If a version change implies a schema change, that part is on you.
- **No dependency resolution.** Modsx doesn't know that `Blog` needs `Users`. Restoring one won't restore the other.
- **No Composer integration.** Third-party packages a module depends on remain your `composer.json`'s problem.
- **Backups are plain directory copies.** No compression, no deduplication. A large module backed up fifty times occupies fifty copies — hence `modsx:prune`.
- **Restore is not fully atomic.** Each directory is moved into place individually. The window is small and the pre-restore backup is your recovery path, but a machine that dies mid-restore can leave some directories updated and others not.

---

FAQ
---

[](#faq)

**Do I need this package to use the convention?**No. That's the point. Prefix your directories and everything works. Install the package when you want backups.

**What happens to my modules if I uninstall it?**Nothing. They are ordinary Laravel directories and were never anything else. Only `modsx-backups/` becomes unmanaged, and that is just files you can keep or delete.

**Does it conflict with `nwidart/laravel-modules`?**The two solve the same problem in incompatible ways, so running both is a bad idea. They won't clash on disk, though: the default backup directory is `modsx-backups/` precisely to stay clear of that package's `Modules/` source tree.

**Can I move a module to another project?**Yes. Copy `modsx-backups/Blog/` into the target project's backup directory and run `php artisan modsx:restore Blog`. Namespaces survive because the directory layout does.

**Why numbered versions instead of timestamps?**They're short, they sort correctly, and they're easy to pick at a prompt. The creation time is in the manifest.

**Can two modules share a directory?**No. A directory belongs to exactly one module — the one its name encodes.

**Is it safe to run in production?**The commands are developer tools. They confirm before destroying anything and refuse to run non-interactively without `--force`, but a deploy pipeline is not where module directories should be moving around.

---

Roadmap
-------

[](#roadmap)

- Optional backup of module-related single files
- ZIP archives as an alternative to directory copies
- `modsx:export` — export a module to a standalone ZIP for distribution

---

Contributing
------------

[](#contributing)

Issues and pull requests welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).

```
composer install
composer test
composer lint
```

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance100

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/320743078?v=4)[Synerdy](/maintainers/Synerdy)[@Synerdy](https://github.com/Synerdy)

---

Top Contributors

[![szajens](https://avatars.githubusercontent.com/u/65465848?v=4)](https://github.com/szajens "szajens (6 commits)")

---

Tags

laravelbackupversioningmodulesrestoreconvention

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/synerdy-modsx/health.svg)

```
[![Health](https://phpackages.com/badges/synerdy-modsx/health.svg)](https://phpackages.com/packages/synerdy-modsx)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M337](/packages/laravel-ai)[laravel/framework

The Laravel Framework.

34.9k556.2M21.6k](/packages/laravel-framework)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)[spatie/laravel-backup

A Laravel package to backup your application

6.0k25.4M274](/packages/spatie-laravel-backup)[laravel/boost

Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.

3.6k26.0M836](/packages/laravel-boost)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9922.4M147](/packages/roots-acorn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
