Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Wn Cms Module Laravel Package

winter/wn-cms-module

Winter CMS's core content management module, providing page templates, layouts, partials, and content blocks on top of Laravel. It powers front-end page management with a Twig-based templating engine, letting developers build and edit dynamic sites through an intuitive admin interface.

View on GitHub
Deep Wiki
Context7

Getting Started

winter/wn-cms-module is the CMS half of Winter CMS — the thing that turns a Laravel app into a theme-driven site: pages, partials, layouts, content files, Twig tags like {% component %} / {% placeholder %}, the AJAX framework, and the backend CMS editor. You will almost never composer require it directly: it arrives as part of wintercms/winter (or the wintercms/wn-* module set) and shows up in your install as modules/cms. The split repo is a read-only mirror — file issues and PRs against wintercms/winter, not here. Expect to use it, not to develop it, unless you're patching a tag parser or the Controller.

Where to look first, in order:

  1. config/cms.php — active_theme, template_max_size, editable_template_extensions, link_policy, and the security switches (safe_mode, enable_unsafe_markup).
  2. modules/cms/classes/ — Controller.php, Router.php, Theme.php, CmsCompoundObject.php (the page/layout/partial base class and its front-matter parsing), ComponentBase.php.
  3. modules/cms/twig/ — Extension.php plus the token parsers for {% page %}, {% partial %}, {% content %}, {% component %}, {% placeholder %}.
  4. Your own theme under themes/<name>/.

First useful thing to build: a layout with {% page %}, {% styles %}, {% scripts %}, one page that uses url, layout and [viewBag] front matter, and one tiny plugin component rendered with {% component %}. That wires up the whole surface area in ~20 lines.

Implementation Patterns

Page front matter is the routing table. Pages are .htm files with INI front matter terminated by a bare == line:

url = "/blog/:slug"
title = "Blog post"
layout = "default"
description = "SEO description"
is_hidden = 0

[blogPost]
slug = "{{ :slug }}"

[viewBag]
sectionClass = "blog"
==
{% component 'blogPost' %}

:slug in url routes the parameter; {{ :slug }} inside a component property block forwards it into the component. Backend-defined [viewBag] values are readable in Twig as {{ viewBag.sectionClass }}. is_hidden = 1 keeps a page out of the backend list without deleting it — handy for partial-like pages driven purely by URL segments.

Components are the unit of PHP-in-template. A component is a class extending Cms\Classes\ComponentBase registered in a plugin's registerComponents(), with markup in components/<name>/default.htm beside the class:

public function defineProperties()
{
    return [
        'limit' => [
            'title' => 'Limit',
            'type' => 'string',
            'default' => 5,
            'validationPattern' => '^[0-9]+$',
            'validationMessage' => 'Numbers only',
        ],
    ];
}

public function onRun()
{
    $this->addJs('assets/js/latest-posts.js');
    $this->page['posts'] = Post::take($this->property('limit'))->get();
}

public function onFilter()
{
    $this->page['posts'] = Post::where(/* ... */)->get();
    return ['#post-list' => $this->renderPartial('@list')];
}

Inside component markup, {{ __SELF__.property('limit') }} reads settings and {% partial '@list' %} resolves a partial inside the component's own directory — the @ prefix is the single most useful bit of component knowledge.

Splitting markup across pages/layouts with placeholders. Layout output goes through {% page %}; pages can inject into named regions:

{# layout: default.htm #}
<head>{% styles %}</head>
<body>{% page %}{% scripts %}</body>

{# a page that needs extra assets #}
{% put styles %}<link href="{{ 'assets/css/blog.css'|theme }}" rel="stylesheet">{% endput %}

Placeholders ({% placeholder hero %}...{% default %}fallback{% enddefault %}{% endplaceholder %}) are the idiomatic way to let a layout expose editable regions without a full page editor. Forget {% styles %}/{% scripts %} in the layout and every addCss()/addJs() call silently vanishes — that's most "assets don't load" reports.

AJAX in the CMS framework. Forms/buttons use data-request, data-request-update, data-request-data, data-request-confirm, data-request-flash, data-request-validate. A component handler returning ['#selector' => $this->renderPartial('@list')] is the reliable pattern; data-request-update="partialName: '#selector'" on the markup side resolves partials through the controller, so keep those partials in the theme (or return HTML from PHP instead of guessing at @ resolution).

URLs and asset URLs are filters, not hardcoded paths. {{ 'blog/post'|page({ slug: 'hello' }) }}, {{ 'assets/css/app.css'|theme }}, {{ 'photos/hero.jpg'|media }}, and {{ 'My theme key'|_ }} for theme lang files in lang/en/lang.php.

Integrating with plugins. Plugins get CMS integration points for free: registerComponents(), registerMarkupTags() (custom Twig functions/filters — the sanctioned way to add helpers, rather than editing the module), and Page::extend() / Theme::extend() in boot() for model-level additions. For hooks at render time, listen on cms.page.beforeDisplay, cms.page.start, cms.page.beforeRenderPage, cms.page.render, cms.page.end, or cms.block.render (partial/placeholder blocks).

Gotchas and Tips

  • It's a module mirror, not a library. Don't add it to a Winter app that already ships it; version skew between the module and winter/wn-system-module breaks in confusing ways. The near-zero dependents count reflects the split-repo packaging, not abandonment.
  • Front matter is INI and == must be alone on its line. Malformed front matter throws a CmsException that surfaces as a blank page or a generic error — check storage/logs/ before assuming the Twig is wrong.
  • Safe mode blocks |raw. cms.enable_unsafe_markup (and cms.safe_mode) gate the raw filter and unsafe template abilities, since anyone who can edit a template could otherwise inject JS for every admin. Turn it on deliberately, per environment, not to make one partial render.
  • Compiled Twig is cached. If an edit doesn't appear, clear the application cache (backend Settings → Cache or php artisan cache:clear) and confirm you edited the theme that's actually active — php artisan theme:list / theme:use <dir> use the directory name, not the name: in theme.yaml.
  • Theme customization values live in the database. config/fields.yaml in the theme defines the editor ({{ this.theme.site_name }} reads it), but saved values persist per environment — a fresh deploy starts from the default: values. Don't treat this.theme as file-config.
  • AJAX handlers are only public function on*() on the component bound to that page, and they must be re-bound after an AJAX partial replaces the DOM. Use delegated listeners or the ajaxUpdateComplete event rather than $(...).on('click') at page load.
  • Aliases and __SELF__. Two instances of the same component need explicit aliases ([blogPost recentPosts]); a component's partial referring to itself should use __SELF__, and @partial not a theme-relative path.
  • Routing precedence: plugin routes.php static routes and Laravel routes win over CMS page URLs. If a page never resolves, check for a conflicting route and whether the page slug is shadowed by a url pattern; pages/404.htm (and error.htm) are your fallbacks, so give them real content early.
  • Pretty URLs need the web server set up (public/.htaccess or the nginx try_files block), otherwise the index.php/... fallback changes every |page URL you produce.
  • Extension points, in preference order: components → registerMarkupTags() → Event::listen('cms.page.*') → Page::extend(). Editing modules/cms directly is a last resort; it's vendor code and gets overwritten on update.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager