PHPackages                             mmi/mmi-cms - 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. [Framework](/categories/framework)
4. /
5. mmi/mmi-cms

ActiveLibrary[Framework](/categories/framework)

mmi/mmi-cms
===========

The CMS written with MMi Framework

6.0.0(1mo ago)828.1k↓38.6%32MITJavaScriptCI passing

Since Sep 19Pushed 1mo ago7 watchersCompare

[ Source](https://github.com/milejko/mmi-cms)[ Packagist](https://packagist.org/packages/mmi/mmi-cms)[ RSS](/packages/mmi-mmi-cms/feed)WikiDiscussions master Synced yesterday

READMEChangelog (10)Dependencies (9)Versions (676)Used By (2)

mmi-cms
=======

[](#mmi-cms)

A headless-capable CMS library built on top of the [MMi Framework](https://github.com/milejko/mmi). It provides a structured content tree (categories), a flexible template/widget system, a REST JSON API, a built-in admin panel (`CmsAdmin`), file management, mail, tagging, cron, and role-based access control — all wired together via [PHP-DI](https://php-di.org/).

---

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

[](#requirements)

DependencyVersionPHP≥ 8.1mmi/mmi^5.0phpmailer/phpmailer^6.0---

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

[](#installation)

```
composer require mmi/mmi-cms
```

Then bootstrap in your application's DI config (e.g. `src/App/di.app.php`). The CMS ships several partial DI files that you include individually:

DI fileWhat it registers`src/Cms/di.cms.php`Core services: `AppEventInterceptorInterface`, `AuthProviderInterface`, `CmsScopeConfig`, `CmsCategoryRepository``src/Cms/di.api.php``MenuServiceInterface`, `StructureServiceInterface``src/Cms/di.auth.php``AuthInterface` (wires `Auth` ↔ `AuthProviderInterface`, injects into `ActionHelper` and `View`)`src/Cms/di.acl.php``AclInterface` with default allow rules for `admin`, `guest`, and `cmsAdmin:index:login``src/Cms/di.navigation.php``Navigation` (requires a `NavigationConfig` binding to be present)Minimal required bindings you must provide in your own DI config:

```
return [
    NavigationConfig::class => autowire(YourNavigationConfig::class),
    CmsSkinsetConfig::class => fn($c) => (new CmsSkinsetConfig())->addSkin(new YourSkinConfig()),
];
```

Deploy the database schema:

```
./bin/mmi Mmi:DbDeploy
```

---

Environment variables
---------------------

[](#environment-variables)

VariableDefaultDescription`CMS_AUTH_SALT``better-use-some-random-salt`Salt used for password hashing — **always override**`CMS_LANGUAGE_DEFAULT``pl`Default admin UI language`CMS_LANGUAGE_LIST``pl,en`Comma-separated list of available languages`CMS_THUMB_QUALITY``85`WebP / JPEG thumbnail quality (%)`APP_DEBUG_ENABLED`—Enable debug mode`APP_VIEW_CDN`—CDN base URL for assets`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_NAME`, `DB_PASSWORD`—Database connection`CACHE_SYSTEM_ENABLED`, `CACHE_PUBLIC_ENABLED`—Cache toggles`SESSION_*`—Session handler configuration`LOG_HANDLER`—Logger handlerCopy `.env.sample` to `.env` and fill in the values.

---

Architecture overview
---------------------

[](#architecture-overview)

```
CmsSkinsetConfig          ← registry of skins (one per frontend site / scope)
  └─ CmsSkinConfig        ← a skin: key, name, attributes, front URL, preview path
       ├─ CmsTemplateConfig   ← a page type; maps to a controller class
       │    ├─ CmsSectionConfig  ← a named content area within a template
       │    │    └─ CmsWidgetConfig  ← a pluggable content block; maps to a controller class
       │    └─ (compatible children keys, allowed-on-root flag, cache TTL)
       └─ (menu max depth, skin attributes passed to the API)

```

### Skins (scopes)

[](#skins-scopes)

A **skin** represents a frontend application (or a section of one). Each skin has a unique `key` used as the scope identifier in API URLs. Multiple skins can share the same backend database.

```
$skin = (new CmsSkinConfig())
    ->setKey('my-site')
    ->setName('My Site')
    ->setFrontUrl('https://my-site.example.com')
    ->setPreviewPath('/preview')
    ->setAttributes(['theme' => 'dark'])
    ->setMenuMaxDepthReturned(3);
```

### Templates

[](#templates)

A **template** defines a page type. Each template maps to a PHP controller that extends `AbstractTemplateController`. Templates declare which sections (and therefore which widgets) they support.

```
$template = (new CmsTemplateConfig())
    ->setKey('article')
    ->setName('Article page')
    ->setControllerClassName(MyArticleController::class)
    ->setAllowedOnRoot(false)
    ->setCompatibleChildrenKeys(['article', 'folder'])
    ->setCacheLifeTime(3600)
    ->addSection(
        (new CmsSectionConfig())
            ->setKey('main')
            ->setName('Main content')
            ->addWidget(
                (new CmsWidgetConfig())
                    ->setKey('text')
                    ->setName('Text block')
                    ->setControllerClassName(MyTextWidgetController::class)
                    ->setMinOccurrence(0)
                    ->setMaxOccurrence(10)
            )
    );

$skin->addTemplate($template);
```

### Template controllers

[](#template-controllers)

Extend `AbstractTemplateController` and implement `getTransportObject()` to return a `TransportInterface` (typically a `TemplateDataTransport`). This is called by the REST API.

```
class MyArticleController extends AbstractTemplateController
{
    public function getTransportObject(): TransportInterface { ... }
}
```

### Widget controllers

[](#widget-controllers)

Extend `AbstractWidgetController` and implement `getDataObject()` returning a `DataInterface` (typically a `WidgetData`). File attachments are available via `getAttachments()` / `getAttachment()`.

```
class MyTextWidgetController extends AbstractWidgetController
{
    public function getDataObject(): DataInterface { ... }
}
```

---

REST API
--------

[](#rest-api)

Routes are registered by `CmsRouterConfig`. All responses are JSON.

MethodURL patternDescription`GET``/api`List all registered skins with HATEOAS links`GET``/api/{scope}`Skin config: attributes, templates, links`GET``/api/{scope}/contents`Flat list of all published categories`GET``/api/{scope}/contents/{uri}`Single category by URI (with widgets)`GET``/api/{scope}/contents/preview/{id}/{originalId}/{authId}/{time}`Unpublished preview by ID`GET``/api/{scope}/contents/preview/{uri}`Published preview by URI`GET``/api/{scope}/structure`Tree structure of categories (navigation)`GET``/api/contents/id/{id}`Redirect resolver: ID → canonical URLResponses for category endpoints include rendered widget data, breadcrumbs, and HATEOAS links. Responses for missing or misconfigured content return `404` with `{"message":"..."}`.

Caching is handled transparently via `CacheInterface`; use `CMS_THUMB_QUALITY` and cache config vars to tune performance.

---

Admin panel
-----------

[](#admin-panel)

The `CmsAdmin` module is mounted at `/cmsAdmin` and provides:

- **Category management** — tree editor, drag-and-drop ordering, ACL per category, trash, widget placement
- **File manager** — upload, thumbnail generation (WebP / JPEG via scale / scalex / scaley / scalecrop)
- **User management** — `CmsAuth` records with roles, password hashing with `CMS_AUTH_SALT`
- **Mail** — mail server config, mail definitions, send log
- **Tags** — tag management and relations
- **Cron jobs** — schedule and manual execution
- **Cache management** — flush from the UI

File serving routes:

RoutePatternDefault thumb`data/default/{hash}{name}.webp`Scaled thumb`data/{scale}/{dimensions}/{hash}{name}.webp`Download`data/download/{hash}{name}/{targetName}`---

Authentication
--------------

[](#authentication)

`AuthProvider` implements `AuthProviderInterface` from the MMi Framework. It:

- Authenticates against the `CmsAuth` ORM table (salted SHA-512 hashes)
- Supports optional LDAP via `Mmi\Ldap`
- Records last login IP (respects `X-Forwarded-For`), last failed IP, and fail counters
- Returns roles from `CmsAuthRecord` (falls back to `['guest']` when empty)

To customise authentication, bind your own implementation to `AuthProviderInterface::class` in your DI config.

---

CLI commands
------------

[](#cli-commands)

CommandClassDescription`Cms:CategoryRebuild``CategoryRebuildCommand`Rebuilds the category path/URI tree`Cms:CronExecute``CronExecuteCommand`Runs all due cron jobs`Cms:FileGarbageCollector``FileGarbageCollectorCommand`Removes orphaned uploaded filesRun via the MMi console:

```
./bin/mmi Cms:CategoryRebuild
./bin/mmi Cms:CronExecute
./bin/mmi Cms:FileGarbageCollector
```

---

Development
-----------

[](#development)

### Running tests

[](#running-tests)

```
composer test:phpunit          # PHPUnit with coverage
composer test:phpstan          # Static analysis (level 1)
composer test:phpcs            # Coding style (phpcs)
composer test:phpmd            # Mess detection
composer test:all              # All of the above + security checker
```

Quick run without coverage:

```
./vendor/bin/phpunit --no-coverage
```

### Code style fixers

[](#code-style-fixers)

```
composer fix:phpcbf            # PHP Code Beautifier
composer fix:php-cs-fixer      # php-cs-fixer on src/ and tests/
composer fix:all               # Both
```

### Docker

[](#docker)

A `Dockerfile` is included for CI and local development:

```
docker build --build-arg PHP_VERSION=8.5 -t mmi-cms .
docker run --rm mmi-cms ./vendor/bin/phpunit --no-coverage
```

---

Links
-----

[](#links)

- [MMi Framework](https://github.com/milejko/mmi) — the underlying MVC framework, DI container, ORM, cache, auth, and HTTP layers
- [PHP-DI](https://php-di.org/) — dependency injection container used for wiring
- [PHPMailer](https://github.com/PHPMailer/PHPMailer) — mail transport

###  Health Score

61

—

FairBetter than 98% of packages

Maintenance90

Actively maintained with recent releases

Popularity33

Limited adoption so far

Community27

Small or concentrated contributor base

Maturity83

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 72.6% 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 ~6 days

Recently: every ~21 days

Total

657

Last Release

53d ago

Major Versions

3.1.9 → 5.2.12022-06-08

3.1.10 → 5.3.02022-11-11

2.8.35 → 5.6.1.22023-05-31

2.6.12.3 → 5.8.262024-05-22

5.8.45 → 6.0.02026-05-11

PHP version history (6 changes)1.0.1PHP &gt;=5.4.0

1.2.1PHP &gt;=5.6.0

2.6.1PHP &gt;=7.0.0

3.2.1PHP &gt;=7.2.0

4.0.0PHP &gt;=7.3.0

5.2.20PHP &gt;=8.0.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/f986340afe26a382bdec4d838f514714b6f5bc7222baf73972001a66b9b6dab0?d=identicon)[emisarius](/maintainers/emisarius)

---

Top Contributors

[![milejko](https://avatars.githubusercontent.com/u/14335568?v=4)](https://github.com/milejko "milejko (1291 commits)")[![rpdiss](https://avatars.githubusercontent.com/u/9515082?v=4)](https://github.com/rpdiss "rpdiss (140 commits)")[![telemetricsystems](https://avatars.githubusercontent.com/u/25298449?v=4)](https://github.com/telemetricsystems "telemetricsystems (128 commits)")[![maqlec](https://avatars.githubusercontent.com/u/13941930?v=4)](https://github.com/maqlec "maqlec (74 commits)")[![kgapskine](https://avatars.githubusercontent.com/u/180635925?v=4)](https://github.com/kgapskine "kgapskine (59 commits)")[![pgalazka](https://avatars.githubusercontent.com/u/6582384?v=4)](https://github.com/pgalazka "pgalazka (19 commits)")[![norbertturek](https://avatars.githubusercontent.com/u/22013071?v=4)](https://github.com/norbertturek "norbertturek (13 commits)")[![cezolk](https://avatars.githubusercontent.com/u/29655613?v=4)](https://github.com/cezolk "cezolk (10 commits)")[![paweljelonek](https://avatars.githubusercontent.com/u/912989?v=4)](https://github.com/paweljelonek "paweljelonek (9 commits)")[![piotrwojewoda](https://avatars.githubusercontent.com/u/39909775?v=4)](https://github.com/piotrwojewoda "piotrwojewoda (8 commits)")[![m-klosinski](https://avatars.githubusercontent.com/u/34100628?v=4)](https://github.com/m-klosinski "m-klosinski (7 commits)")[![mariuszjarzab](https://avatars.githubusercontent.com/u/17081823?v=4)](https://github.com/mariuszjarzab "mariuszjarzab (6 commits)")[![funfel](https://avatars.githubusercontent.com/u/14870094?v=4)](https://github.com/funfel "funfel (5 commits)")[![kgapski](https://avatars.githubusercontent.com/u/12533441?v=4)](https://github.com/kgapski "kgapski (4 commits)")[![tymoteusz-be](https://avatars.githubusercontent.com/u/9280702?v=4)](https://github.com/tymoteusz-be "tymoteusz-be (3 commits)")[![Wojciechem](https://avatars.githubusercontent.com/u/4303141?v=4)](https://github.com/Wojciechem "Wojciechem (2 commits)")[![MKalat](https://avatars.githubusercontent.com/u/1298567?v=4)](https://github.com/MKalat "MKalat (1 commits)")

---

Tags

cmsmmi

### Embed Badge

![Health badge](/badges/mmi-mmi-cms/health.svg)

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

###  Alternatives

[getkirby/cms

The Kirby core

1.5k584.8k472](/packages/getkirby-cms)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.7k38.9k](/packages/matomo-matomo)[sproutcms/cms

Enterprise content management and framework

242.5k4](/packages/sproutcms-cms)[chameleon-system/chameleon-base

The Chameleon System core.

1028.6k5](/packages/chameleon-system-chameleon-base)[doppar/framework

The Doppar Framework

4012.4k14](/packages/doppar-framework)[impresscms/impresscms

ImpressCMS is an open source content management system with a focus on security and speed

291.1k](/packages/impresscms-impresscms)

PHPackages © 2026

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