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

Cms Laravel Package

grafite/cms

Grafite CMS is a Laravel-based content management system with an admin dashboard for managing pages, blog posts, media, menus, and custom forms. It offers a clean, customizable interface that integrates smoothly into existing Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you install. grafite/cms was archived in 2017 and targets the Laravel 5.2–5.4 era (PHP 7.0/7.1, grafite/crud, grafite/builder, Intervention Image). v2.3.0, the newest release, is titled "New Laravel 5.4 support" — that is the ceiling, not a step toward modern Laravel. There is no 5.5+, no PHP 8, and no further releases coming. Treat it as a vendor-pinned legacy dependency, not something you bolt onto a current Laravel app. Pin exact versions in composer.json (v2.3.0 is the version to pin — it's the most complete of a dead line), commit the lock file, and ideally isolate the CMS in its own app rather than sharing a codebase with a modern service.

Minimal install path.

composer require grafite/cms:2.3.0
  1. Register Grafite\Cms\Providers\CmsServiceProvider::class (and the Cms facade alias) in config/app.php.
  2. Publish config, views, and migrations:
    php artisan vendor:publish --provider="Grafite\Cms\Providers\CmsServiceProvider"
    
  3. Run the package installer (creates the admin user, seed data, storage folders) — php artisan grafite:install — then php artisan migrate.
  4. Confirm you're on Laravel 5.4 and PHP 7.1 before going further. If the host forces a newer runtime, stop and reassess; v2.3.0 will not run cleanly.
  5. Point APP_URL at the site, ensure public/ is writable for uploaded media, and confirm the admin panel loads at the configured cms_url (typically /admin).

Where to look first, in this order:

Location Why
config/grafite-cms.php Single source of truth: admin URL, front-end template, page style (block vs slim), blog settings, menu registration, analytics toggle
routes/ + php artisan route:list See the admin routes and the front-end catch-all that resolves slugs
resources/views/vendor/grafite-cms/ The published templates — this is where you actually design pages
Grafite\Cms\Services\CmsService The API surface the front end and your own code call into
The generated admin controllers Everything CRUD-related is generated into app/, so it's yours to edit
The history/revert screens (new in 2.3.0) Content versioning — know it exists before an editor asks "can we undo this?"

First use case — a marketing page in ten minutes. Log into /admin, create a Page, choose a template (default), give it the slug about, and drop content into the block editor. Then in resources/views/vendor/grafite-cms/templates/default/ (or your own layout) render it and hit /about. Edit it a second time, then use the new history screen to revert to the first version — that round trip, admin → block content → version history → front-end template, is the core loop you'll live in.

Implementation Patterns

Front-end resolution via a catch-all route. The CMS owns slug resolution; don't hand-write a route per page. Keep your catch-all last in routes/web.php and let CmsService look up a Page by slug, then fall through to blog/event/module routes. In your layout:

{{-- layout --}}
<title>{{ $page->title ?? config('app.name') }}</title>
{!! $page->blocks !!}

Block mode vs. slim mode. page_style => 'block' gives editors the WYSIWYG block editor and stores HTML — fast for content teams, less control for you, and now versioned by the 2.3.0 history feature so a bad edit is recoverable. page_style => 'slim' (one Blade file per template, selected in the admin) gives you full markup control and is the right choice for anything with real layout requirements. Most production sites end up hybrid: slim templates for landing pages, block mode for blog-adjacent content.

Content history and revert (2.3.0). Every save now leaves a revision you can inspect and roll back to. Two practical consequences: (a) tell editors it exists — it turns "restore from backup" incidents into a two-click fix; (b) the history tables grow without bound and there is no retention policy shipped. Add a scheduled prune command (keep N revisions or M days) or you'll be paying for a database that's mostly old copies of paragraph three. Also verify what revert actually restores: for modules with relations (galleries, tags), reverting the parent record may not restore associated rows — test on one module before promising editors full rollback.

Custom modules are the real value. The day-to-day leverage isn't pages — it's php artisan grafite:crud. Point it at a table, it scaffolds model + controller + views + routes + admin menu entry, wired into the same admin chrome as Pages and Blogs. A "Team Members" or "Locations" module that would take a day becomes a 15-minute scaffold plus styling. Commit before running it every time; it writes into app/Http/Controllers/Grafite/Cms/ and mutates config. 2.3.0 fixed parts of module generating, so re-run any module you'd previously hand-patched and diff carefully — the fix may change output you'd already worked around.

Package modules for reuse (new in 2.3.0). The module composer packager lets you export a generated module as a Composer package. On a multi-site estate running this CMS, that's the single most useful addition in the release: build "Team Members" once, install it on every site. Discipline required — version the package, keep site-specific config in the host app's config/grafite-cms.php rather than inside the package, and remember the packaged module still depends on the archived grafite/* trio. It solves duplication, not the archive risk.

Consume CMS data directly in app code. You don't have to go through the front-end resolver. The models are plain Eloquent:

$posts   = \Grafite\Cms\Models\Blog::where('is_published', 1)->orderBy('published_at', 'desc')->paginate(10);
$gallery = \Grafite\Cms\Models\Image::where('gallery_id', $id)->get();

Use this for sidebars, footers, sitemaps, JSON feeds, and API endpoints — anything the built-in templates don't cover. This is also how you'd surface analytics data outside the dashboard.

Blog pipeline. Blogs carry tags, categories, publish dates, and a featured image. Wire the index route to pagination, add a tag route that filters, and generate sitemap.xml/RSS by iterating Blog and Page records in a scheduled command. This is the highest-value integration because editors expect it and the package gives you 90% for free.

Internal analytics (2.3.0). A first-party page-view component now ships with the CMS — no Google Analytics dependency, which matters on locked-down or privacy-sensitive sites. Two caveats: it writes to your legacy database (size it before enabling, and decide retention up front), and it's page-view counting, not an analytics platform. Decide deliberately whether you enable it or keep your existing client-side tool; running both is just duplicated noise. If GDPR/consent obligations apply, verify what it stores (IP? user agent?) before turning it on.

Media and images. Uploads land under public/ via the media manager; Intervention handles resize/crop. Standardize on a config-driven set of derivatives (thumb/hero/og) applied in a small service or model accessor rather than calling Image::make() ad hoc in templates — otherwise every new template invents its own sizes and you get a public/ folder full of one-off crops.

Admin customization. Override views through vendor:publish into resources/views/vendor/grafite-cms/, register menu items in config/grafite-cms.php, and extend generated controllers directly — the package deliberately puts scaffolding in your app so you can edit it. Re-brand via your own layout plus a small CSS override; don't patch vendor files.

Deployment workflow. composer install --no-dev --optimize-autoloader → php artisan migrate --force → config:cache → route:cache → republish assets. Because the CMS owns a catch-all route, verify route caching still resolves slugs in staging; if it breaks, drop route:cache for this app and rely on config:cache only. Note the 2.3.0 rollback change below — your deploy rollback plan can no longer assume the CMS creates an archive for you.

Gotchas and Tips

The archive is the headline risk. No security patches, no PHP 8 compatibility, no Laravel 5.5+ support, zero dependents maintaining it. v2.3.0 arriving doesn't change that — a release on a dead line is still a dead line. If you inherit a site running it, the assessment should be "stabilize on 2.3.0 and plan migration," not "extend." Freeze PHP and Laravel versions, pin the lock file, and keep composer update off the table. If you're choosing it today for greenfield work, don't — the scaffold-generator idea is worth copying, not the dependency.

Rollback no longer creates an archive (2.3.0 behaviour change). If any runbook, CI job, or muscle memory depended on a rollback producing an archive you could restore from, that safety net is gone. Replace it explicitly: take a database dump before migrations and before any grafite:crud run, and keep restore instructions in the repo. This is the one change in 2.3.0 most likely to bite silently.

Version lockstep across the Grafite trio. grafite/cms, grafite/crud, and grafite/builder are tightly coupled. Mismatched minors produce confusing failures in the admin forms (missing fields, blank CRUD screens) rather than clean errors. Pin all three explicitly — including crud/builder versions contemporaneous with 2.3.0 — and test the admin after any change. The 2.3.0 module-generator fixes are exactly the kind of thing that assumes a matching crud version.

Catch-all route ordering. The slug resolver must be registered last. Adding a new resource route after the CMS routes silently shadows it, and you'll see pages rendered where your controller should be — or 404s on routes that route:list clearly shows. This is the single most common "the CMS is broken" report.

Block content is raw HTML. {!! $page->blocks !!} is an XSS vector if anything but trusted editors can write pages. Restrict admin access at the network layer or via middleware, and sanitize on output for any module where non-admins contribute. There is no built-in sanitizer — and history/revert now means an injected payload can persist across revisions, so cleaning one page isn't enough once it's in the revision table.

Slug collisions. Pages, blogs, events, and custom modules can all claim the same slug, and the resolution order is fixed by the package. Enforce uniqueness per-module in the admin, and reserve paths (api, admin, sitemap.xml) so a page named "api" doesn't hijack your routes.

grafite:crud rewrites files. Run it in a clean working tree, review git diff before committing, and expect it to touch config/grafite-cms.php for the menu. Running it twice on the same table produces duplicated menu entries and conflicting controller methods. Run it before packaging a module, not after — the packager snapshots whatever state the generated files are in.

Debugging checklist, in order: storage/logs/laravel.log → php artisan route:list | grep -i admin → confirm the published config still matches what you expect (config caching masks edits) → php artisan view:clear after template changes → verify public/ permissions and the storage symlink for missing images. Nine out of ten issues are the catch-all, a cached config, or a permission. For 2.3.0-specific oddities (history, revert, analytics counts), check the migrations actually ran — those tables are new and a partial vendor:publish leaves the UI present but the backend broken.

Extension points worth knowing. Custom modules (the primary one), the module composer packager for distributing them, the CmsService/Cms facade, published views, the config menu array, and middleware on the admin routes. There's still no meaningful event/observer API — if you need lifecycle hooks (cache invalidation, analytics enrichment, history pruning), bind observers to the Eloquent models directly rather than hunting for package hooks that aren't there.

Caching will bite you. Content edits appear instantly in dev and not at all in prod if you've cached views/config aggressively. Add a post-deploy cache clear, and if you add your own full-page cache, invalidate on CMS save and on revert via a model observer — the package won't do it for you, and a revert that doesn't purge your cache looks to editors like the revert failed.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums