PHPackages                             artisanpack-ui/cms-framework - 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. artisanpack-ui/cms-framework

ActiveLibrary[Framework](/categories/framework)

artisanpack-ui/cms-framework
============================

Adds in the back end support for building a CMS with any front end framework.

2.7.2(2w ago)01.0k[25 issues](https://github.com/ArtisanPack-UI/cms-framework/issues)1MITPHPPHP ^8.3

Since Jul 13Pushed 2w agoCompare

[ Source](https://github.com/ArtisanPack-UI/cms-framework)[ Packagist](https://packagist.org/packages/artisanpack-ui/cms-framework)[ RSS](/packages/artisanpack-ui-cms-framework/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (145)Versions (40)Used By (1)

ArtisanPack UI CMS Framework
============================

[](#artisanpack-ui-cms-framework)

[![Latest Version on Packagist](https://camo.githubusercontent.com/d1e9eec884fc4c03a26e5339b8c1b28c754e9f39ea7262e2ccd7888c27446e4f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6172746973616e7061636b2d75692f636d732d6672616d65776f726b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/artisanpack-ui/cms-framework)[![GitHub Tests Action Status](https://camo.githubusercontent.com/965163d17491533b7c2794218c988b88c0dea3887eaa716f9ca34476c43b10ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6172746973616e7061636b2d75692f636d732d6672616d65776f726b2f72756e2d74657374733f6c6162656c3d7465737473)](https://github.com/artisanpack-ui/cms-framework/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/7fa93784005200dc8774a3d9125103119870bd1e8189007730f734be97fcd900/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6172746973616e7061636b2d75692f636d732d6672616d65776f726b2f436865636b253230262532306669782532307374796c696e673f6c6162656c3d636f64652532307374796c65)](https://github.com/artisanpack-ui/cms-framework/actions?query=workflow%3A%22Check+%26+fix+styling%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/c4b5d9784762dee910e3c46ef178d399c1d2ad598fbd451fa10f15cc74110f94/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6172746973616e7061636b2d75692f636d732d6672616d65776f726b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/artisanpack-ui/cms-framework)

A comprehensive Laravel package that provides back-end support for building a CMS with any front-end framework. This package offers a complete set of features for content management, user management, authentication, and more.

Features
--------

[](#features)

- **Content Management**: Content types, taxonomies, and media management
- **Admin Interface**: Admin pages, dashboard widgets, and settings management
- **User Management**: User roles, permissions, and profiles (powered by `artisanpack-ui/rbac` in 2.0.0)
- **Authentication**: Two-factor authentication with Laravel Sanctum integration
- **Notifications**: Comprehensive notification system
- **Site Editor** *(new in 2.0.0)*: WordPress-style templates, template parts, patterns, global styles, and menus
- **Visual Editor Integration** *(new in 2.0.0)*: Opt-in bridge to the optional `artisanpack-ui/visual-editor` package
- **Blog Comments** *(new in 2.1.0)*: Threaded post comments with public read / guest submit, rate-limited public submission, and auth-gated moderation
- **Themes**: Discovery, activation, ZIP upload, and lifecycle hooks
- **Plugins**: Plugin system with lifecycle management, hooks, custom field types, admin pages, and Module Federation support — see the [Plugin Author Guide](./docs/plugin-authoring.md)
- **Core Updates**: Automatic update checking and management with rollback support
- **PWA Support**: Progressive Web App features
- **Audit Logging**: Track changes and user actions

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

[](#requirements)

- PHP 8.3 or higher
- Laravel 12.0 or 13.0
- Laravel Sanctum 4.1 or higher

Quick Installation
------------------

[](#quick-installation)

You can install the CMS Framework package by running the following composer command:

```
composer require artisanpack-ui/cms-framework
```

After installation, publish the configuration file:

```
php artisan vendor:publish --tag=cms-framework-config
```

Run the migrations to set up the database tables:

```
php artisan migrate
```

Documentation
-------------

[](#documentation)

📚 **[Complete Documentation](docs/)**

- **[Installation Guide](docs/installation.md)** - Detailed installation and setup instructions
- **[Configuration](docs/configuration.md)** - Configuration options and environment setup
- **[Usage Guide](docs/usage.md)** - Comprehensive usage examples and tutorials
- **[API Documentation](docs/api.md)** - Complete REST API reference
- **[Migration Guide](docs/migration.md)** - Migrating from other CMS frameworks
- **[Testing](docs/testing.md)** - Testing strategies and examples
- **[Performance &amp; Troubleshooting](docs/performance.md)** - Optimization and common issues
- **[Contributing](docs/contributing.md)** - Development and contribution guidelines

Quick Start
-----------

[](#quick-start)

### Content Types

[](#content-types)

Register a custom content type:

```
use ArtisanPackUI\CMSFramework\Features\ContentTypes\ContentTypeManager;

app(ContentTypeManager::class)->register('product', [
    'name' => 'Product',
    'plural' => 'Products',
    'description' => 'Products for the store',
    'supports' => ['title', 'editor', 'thumbnail'],
]);
```

### Admin Pages

[](#admin-pages)

Register a custom admin page:

```
use ArtisanPackUI\CMSFramework\Features\AdminPages\AdminPagesManager;

app(AdminPagesManager::class)->addPage([
    'title' => 'Custom Settings',
    'slug' => 'custom-settings',
    'callback' => function() {
        return view('custom.settings');
    }
]);
```

### Settings

[](#settings)

Register and retrieve settings:

```
use ArtisanPackUI\CMSFramework\Features\Settings\SettingsManager;

// Register a setting
app(SettingsManager::class)->register('site_name', 'My Awesome Site');

// Get a setting
$siteName = app(SettingsManager::class)->get('site_name');
```

### Customization with Hooks

[](#customization-with-hooks)

The CMS Framework uses hooks and filters for extensive customization:

```
// addFilter and addAction are global helper functions provided by the framework

// Add a filter
addFilter('ap.cms.migrations.directories', function($directories) {
    $directories[] = __DIR__ . '/database/migrations';
    return $directories;
});

// Add an action
addAction('ap.cms.after_content_save', function($content) {
    // Do something after content is saved
});
```

#### Content-type lifecycle hooks *(new in 2.5.0)*

[](#content-type-lifecycle-hooks-new-in-250)

Every `Post` and `Page` save, publish, soft delete, and restore emits a namespaced action so host apps and plugins can react without observing Eloquent events directly.

HookFiresPayload`ap.cmsFramework.post.saving`Before a `Post` is written (any create or update).`(Post $post)``ap.cmsFramework.post.saved`After a `Post` is written.`(Post $post)``ap.cmsFramework.post.published`After a save that transitions `status` to `ContentStatus::Published` from a non-published value (first-save-as-published, draft→published, scheduled→published all count). Does **not** fire on subsequent saves of an already-published post.`(Post $post)``ap.cmsFramework.post.trashed`After a soft delete. Skipped on `forceDelete()`.`(Post $post)``ap.cmsFramework.post.restored`After a soft-deleted `Post` is restored.`(Post $post)``ap.cmsFramework.page.saving` / `.saved` / `.published` / `.trashed` / `.restored`Same fire semantics as their post-side counterparts, applied to `Page`.`(Page $page)````
addAction('ap.cmsFramework.post.published', function ($post): void {
    // Warm caches, send a broadcast, publish to a search index, etc.
});
```

Adopting models pull the fire sites in through the `FiresLifecycleHooks` concern under `ArtisanPackUI\CMSFramework\Modules\ContentTypes\Models\Concerns\FiresLifecycleHooks`; declaring `public static function lifecycleHookPrefix(): string` on any custom content-type model wires the same five actions under whatever dotted namespace you return.

#### Dashboard, plugin, and search hooks *(new in 2.5.0)*

[](#dashboard-plugin-and-search-hooks-new-in-250)

HookTypeFiresPayload`ap.cmsFramework.admin.dashboardWidgets`filter`AdminWidgetManager::getAvailableWidgetsForUser()` — after capability filtering, before the widget array is returned.`(array $widgets, ?User $user)``ap.cmsFramework.plugin.hookRegistered`actionImmediately after a plugin's service provider registers during `PluginManager::activate()`. The `$hooks` argument is the plugin's optional `hooks` field from `plugin.json` (empty array when absent).`(string $pluginSlug, array $hooks)``ap.cmsFramework.search.query`filter`HasContentFilters::applySearchFilter()` — used by `BlogManager::getArchiveQuery()` and `PageManager::getPageQuery()` after the default title/content/excerpt LIKE clauses attach. Subscribers can narrow the query, add joined-column matches, or short-circuit to an external search backend.`(Builder $q, string $term, array $context)` where `$context = ['manager' => ..., 'model' => ..., 'filters' => ...]````
addFilter('ap.cmsFramework.admin.dashboardWidgets', function (array $widgets, ?User $user): array {
    if ($user?->hasRole('editor')) {
        $widgets['content-queue'] = ['title' => 'Content queue'];
    }
    return $widgets;
});

addFilter('ap.cmsFramework.search.query', function ($query, string $term, array $context) {
    if (Post::class === $context['model']) {
        $query->orWhereHas('tags', fn ($q) => $q->where('name', 'like', "%{$term}%"));
    }
    return $query;
});
```

Using with the visual editor
----------------------------

[](#using-with-the-visual-editor)

cms-framework is fully usable standalone, but pairs with [`artisanpack-ui/visual-editor`](https://github.com/ArtisanPack-UI/visual-editor) to expose its `Post` and `Page` content to a Gutenberg-style block editor, back `core/site-*` blocks with real site settings, and seed `visual_editor.*` permissions into the RBAC tables. The full integration contract lives in visual-editor's [`docs/plans/12-cms-framework-integration.md`](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/docs/plans/12-cms-framework-integration.md); the reciprocal narrative is visual-editor's [Using with cms-framework](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/README.md#using-with-cms-framework) section.

### Install both packages

[](#install-both-packages)

```
composer require artisanpack-ui/cms-framework artisanpack-ui/visual-editor
```

Both packages are loosely coupled — every cms-framework hook into the editor is guarded by `class_exists(\ArtisanPackUI\VisualEditor\VisualEditor::class)`, so cms-framework continues to work standalone if visual-editor is not installed.

### Run migrations

[](#run-migrations)

```
php artisan migrate
```

The cms-framework migration set adds a `block_content json nullable` column to the `posts` and `pages` tables (alongside the preserved legacy `content` longText column, which keeps powering search / excerpt / backwards-compatibility). Editing a legacy post in the visual editor populates `block_content` on first save; the legacy column is left untouched. See plan 12 [§4.2 Block content storage](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/docs/plans/12-cms-framework-integration.md#42-block-content-storage) for the dual-state guidance.

### Resource map (`ap.visual-editor.resources`)

[](#resource-map-apvisual-editorresources)

When visual-editor is detected, cms-framework's service provider auto-registers `Post` and `Page` into the editor's `ap.visual-editor.resources` filter:

```
// CMSFrameworkServiceProvider::boot()
if ( class_exists( \ArtisanPackUI\VisualEditor\VisualEditor::class ) ) {
    addFilter( 'ap.visual-editor.resources', function ( array $resources ): array {
        return array_merge( [
            'posts' => \ArtisanPackUI\CMSFramework\Modules\Blog\Models\Post::class,
            'pages' => \ArtisanPackUI\CMSFramework\Modules\Pages\Models\Page::class,
        ], $resources );
    } );
}
```

Host-app overrides in `config/artisanpack/visual-editor.php` always win on key collision, so swapping `posts` to a custom `App\Models\Post` is just a config edit. Full filter contract (input/output shape, collision behavior, validation guarantees, contributor timing): plan 12 [§4.1 Resource filter contract](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/docs/plans/12-cms-framework-integration.md#41-resource-filter-contract).

### PostResolver accessors

[](#postresolver-accessors)

`Post` and `Page` expose a handful of accessors that visual-editor's `PostResolver` reads to stamp `_resolved*` attributes on `core/post-*` and `core/navigation` blocks:

AccessorTypeSourceUsed by`rendered_content``string``HasRenderedBlockContent` concern — renders `block_content` through the visual editor's server-side renderer, falling back to the legacy `content` column when `block_content` is null`_resolvedContent` on `core/post-content``previous_post``?Post`adjacent post ordered by `published_at` (ties broken by `id`)`_resolvedPreviousPost` on `core/post-navigation-link``next_post``?Post`adjacent post ordered by `published_at` (ties broken by `id`)`_resolvedNextPost` on `core/post-navigation-link``comments_count``int`approved comments only`_resolvedCommentsCount` on `core/post-comments-count``comments_url``string`canonical `#comments` anchor on the post URL`_resolvedCommentsUrl` on `core/post-comments-link`All accessors are eager-load friendly and safe to call on detached models — the adjacency lookup short-circuits to `null` for unsaved or unpublished posts.

### Site-meta bridge

[](#site-meta-bridge)

cms-framework registers the `site.*` setting family via `SettingsManager` and exposes a WordPress-shape envelope at `GET /api/v1/settings/site` that the editor's `core/site-*` blocks consume:

```
$settings->registerSetting( 'site.title',    config( 'app.name', '' ),  'sanitizeText',  SettingType::String );
$settings->registerSetting( 'site.tagline',  '',                         'sanitizeText',  SettingType::String );
$settings->registerSetting( 'site.url',      config( 'app.url', '' ),    'sanitizeUrl',   SettingType::String );
$settings->registerSetting( 'site.logo_id',  null,                       'sanitizeInt',   SettingType::Integer );
$settings->registerSetting( 'site.icon_id',  null,                       'sanitizeInt',   SettingType::Integer );
```

```
GET /api/v1/settings/site
{
    "title": "ArtisanPack UI Demo",
    "description": "A Laravel CMS",
    "url": "https://example.test",
    "site_logo": 42,
    "site_icon": 17
}
```

Mutating endpoints (`PUT /api/v1/settings/site`) update the matching `Setting` rows through `SettingsManager`. See plan 12 [§4.3 Site-meta REST shape](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/docs/plans/12-cms-framework-integration.md#43-site-meta-rest-shape).

### Permissions seed (`visual_editor.*`)

[](#permissions-seed-visual_editor)

When visual-editor is detected, cms-framework seeds the editor's permission family into its RBAC tables so host apps can grant them on roles immediately:

```
visual_editor.access
visual_editor.posts.edit
visual_editor.pages.edit
visual_editor.templates.edit
visual_editor.template-parts.edit
visual_editor.patterns.edit
visual_editor.global-styles.edit
visual_editor.navigation.edit

```

Permissions are registered but not yet enforced by visual-editor's policies — that flips in a future release behind `artisanpack.visual-editor.authorization.delegate_to_cms_framework`. See plan 12 [§4.6 Permissions seed (G5)](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/docs/plans/12-cms-framework-integration.md#46-permissions-seed-g5).

### Version pairing

[](#version-pairing)

cms-framework and visual-editor ship as a version pair — the supported combinations are tracked in visual-editor's [Version compatibility](https://github.com/ArtisanPack-UI/visual-editor/blob/release/1.0/README.md#version-compatibility) matrix. Bumping the major on either package without bumping the partner is unsupported.

AI features
-----------

[](#ai-features)

The framework ships five AI-powered content-authoring agents (added in 2.3.0). Each maps to a feature key in the `artisanpack-ui/ai` foundation and is registered automatically via the service provider's `aiFeatures()` hook — no wiring required in host apps.

Feature keyAgentWhat it does`cms.post_title``PostTitleSuggestionAgent`Generate 3–5 title variants from a draft body.`cms.excerpt``ExcerptGenerationAgent`Generate a natural excerpt (default ≤200 chars) from full content.`cms.suggest_tags``TagSuggestionAgent`Select tags from an existing taxonomy (optionally propose new ones).`cms.suggest_category``CategorySuggestionAgent`Pick a single category (as a slash-delimited path) from a tree.`cms.suggest_slug``SlugSuggestionAgent`Produce an SEO-friendly slug (short, keyword-forward, no stop words).Every agent honors the feature toggle configured in `artisanpack.ai.features..enabled` — when the toggle is off, the agent throws `FeatureDisabledException` and no model call is made.

### Trigger surfaces

[](#trigger-surfaces)

The same five agents are exposed to every front-end framework:

- **Livewire**: dispatch `ap-cms-ai:{action}` browser events at the `ap-cms-ai-tools` component (`ArtisanPackUI\CMSFramework\Livewire\Ai\AiTools`). Results come back on `ap-cms-ai:{featureKey}:{success|invalid-input|disabled|missing-credentials|error}`.
- **React / Vue / anything else**: `POST` to the REST endpoints mounted under `/api/v1/cms/ai` (`AiController`). This keeps `@artisanpack-ui/react` and `@artisanpack-ui/vue` framework-agnostic — those packages do NOT need to know anything about CMS AI features.

Available endpoints (all `auth` middleware, JSON body):

MethodPathBodyGET`/api/v1/cms/ai/features`—POST`/api/v1/cms/ai/post-title``{ content, tone?, count? }`POST`/api/v1/cms/ai/excerpt``{ content, max_chars? }`POST`/api/v1/cms/ai/suggest-tags``{ content, available_tags, allow_new?, max_selected? }`POST`/api/v1/cms/ai/suggest-category``{ content, category_tree }`POST`/api/v1/cms/ai/suggest-slug``{ title, excerpt?, max_chars? }`Error responses use consistent status codes: `403 feature_disabled`, `422 invalid_input`, `503 missing_credentials`, `500 internal_error`.

### Requirements

[](#requirements-1)

The AI features require `artisanpack-ui/ai ^1.0`. It's declared as a `suggest` in `composer.json` — install it explicitly to opt in:

```
composer require artisanpack-ui/ai:^1.0
```

Without it, the agents, the Livewire component, and the REST endpoints stay unregistered — the framework boots normally.

Plugin System
-------------

[](#plugin-system)

Plugins extend the framework with new admin pages, nav entries, hook subscriptions, custom field types, edit-screen panels, and — in hosts that ship a Module Federation runtime — React admin components.

- Start with the [Plugin Author Guide](./docs/plugin-authoring.md).
- Copy the [`examples/hello-world-plugin/`](./examples/hello-world-plugin/) skeleton for a working reference.

Experimental Features
---------------------

[](#experimental-features)

The following features are **experimental** and should be used with caution in production environments:

### Theme System

[](#theme-system)

The theme system allows customization of the CMS appearance.

**What Works:**

- Theme manager with theme discovery
- Theme activation mechanism
- JSON manifest validation
- Basic theme structure
- Lifecycle hooks for install/activate (see below)

**Known Limitations:**

- Asset compilation not implemented
- No child theme support
- Limited theme customization API
- No theme preview functionality

**Theme Lifecycle Hooks**

The Themes module fires `doAction()` callbacks around `installFromZip()` and `activateTheme()` so host applications can subscribe listeners (seed content, register theme-supplied service providers, etc.) without forking the framework.

HookFiresPayloadThrowing semantics`theme.installing`After the ZIP is extracted and the manifest is validated, before the install is finalized.`string $slug, array $manifest`Throwing aborts the install; the extracted directory is rolled back.`theme.installed`After the install completes and the discovery cache is cleared.`string $slug, array $manifest`Throwing propagates to the caller (install is already complete).`theme.activating`After the target theme is resolved, before `themes.activeTheme` is updated.`string $slug, array $manifest`Throwing aborts activation; the active theme setting is not changed.`theme.activated`After `themes.activeTheme` is updated and cache invalidation is attempted (including `view:clear`, which is logged-and-continued on failure).`string $slug, array $manifest`Throwing propagates to the caller (activation is already complete).```
addAction('theme.activated', function (string $slug, array $manifest): void {
    // Seed pages, register navigation entries, etc.
});
```

**Recommendation:** Use for testing and development. Full theme support including asset compilation and child themes will be added in a future release.

### Reporting Issues

[](#reporting-issues)

If you encounter issues with experimental features, please report them on our [issue tracker](https://github.com/ArtisanPack-UI/cms-framework/issues) with the `experimental` label.

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

[](#contributing)

We welcome contributions! Please see **[Contributing Guide](docs/contributing.md)** for details on:

- Development setup
- Code style guidelines
- Testing requirements
- Submission process

Security
--------

[](#security)

If you discover a security vulnerability, please send an email to . All security vulnerabilities will be promptly addressed.

Credits
-------

[](#credits)

- [Jacob Martella](https://github.com/jacobmartella)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

Changelog
---------

[](#changelog)

Please see **[CHANGELOG.md](CHANGELOG.md)** for more information on what has changed recently.

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance97

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity64

Established project with proven stability

 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 ~15 days

Recently: every ~1 days

Total

26

Last Release

16d ago

Major Versions

0.2.4 → 1.0.0-beta12025-12-22

1.1.0 → 2.0.02026-05-27

PHP version history (2 changes)0.1.0PHP ^8.2

2.3.0PHP ^8.3

### Community

Maintainers

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

---

Top Contributors

[![ViewFromTheBox](https://avatars.githubusercontent.com/u/8247489?v=4)](https://github.com/ViewFromTheBox "ViewFromTheBox (370 commits)")

---

Tags

blogcmscontent-managementheadless-cmslaravellaravel-cmslaravel-packagepagesphpthemes

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/artisanpack-ui-cms-framework/health.svg)

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

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.8k2.5k](/packages/unopim-unopim)[bagisto/bagisto

Bagisto Laravel E-Commerce

28.0k175.2k9](/packages/bagisto-bagisto)

PHPackages © 2026

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