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

craftcms/cms

Craft CMS is a flexible, self-hosted PHP content management system for building custom digital experiences. It offers an intuitive control panel, Twig templating, an auto-generated GraphQL API, built-in ecommerce, and a robust plugin ecosystem.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the mental model shift. craftcms/cms is not a package you composer require into a Laravel app. It is the application — a Yii2-based monolith that owns routing, DI, migrations, the admin UI, and the ORM. There is no Artisan, no Eloquent, no Blade, no app/Models. Everything you know about Composer, .env, and PSR-4 still applies; almost nothing else does. The correct entry point is the starter project:

composer create-project craftcms/craft my-site
cd my-site
php craft install        # or run the web-based setup wizard
php craft up             # applies migrations + project config

Where to look first, in this order:

  1. config/general.php — environment behavior (devMode, allowAdminChanges, securityKey).
  2. config/project/*.yaml — the project config. This is Craft's schema-as-code: sections, fields, entry types, GraphQL schemas. It is the single most important artifact in the repo.
  3. templates/ — Twig front end. templates/index.twig is the home page; routing is file-based plus config/routes.php.
  4. modules/ — where your bespoke PHP goes.
  5. web/index.php — the only web-exposed PHP file.
  6. .env / .env.example — DB creds, CRAFT_ENVIRONMENT, CRAFT_SECURITY_KEY.

First use case (30 minutes): model a blog and render it.

  • CP → Settings → Sections → New channel blog, one entry type article, with a body (Redactor/CKEditor) and heroImage (Assets) field.
  • Add an entry, then in templates/blog/index.twig:
{% for entry in craft.entries.section('blog').limit(10).all() %}
  <a href="{{ entry.url }}">{{ entry.title }}</a>
  {{ entry.heroImage.one().url ?? '' }}
{% endfor %}

That loop is the whole value proposition: content modeling in the CP, zero PHP for the read path. Commit config/project/ immediately after — that diff is your deploy.

Console is your daily driver:

php craft up                        # migrate + apply project config (deploy step)
php craft project-config/apply      # sync YAML without migrations
php craft resave/entries --section=blog --set=title
php craft queue/listen              # required for transforms, resaves, mail
php craft clear-caches/all

Implementation Patterns

Project config as the deployment contract. Treat config/project/ like Laravel migrations: never hand-edit YAML in prod, always promote from a lower env. The workflow is local CP changes → git diff config/project → commit → CI runs php craft up. Enable allowAdminChanges => false in staging/production so nobody can drift the schema through the UI.

Multi-environment config without a config library. Craft 5.9 supports env-var interpolation inline in settings, which removes most switch (Craft::$app->env) blocks:

// config/general.php
'aliases' => [
    '@assetBaseUrl' => 'https://cdn.example.com/$CDN_ENV/',
],
'devMode' => App::env('CRAFT_ENVIRONMENT') === 'dev',

CRAFT_SITE and CRAFT_SITE_UPPER are resolved at runtime, so per-site settings no longer need duplicated blocks.

Element queries are your repository layer. Learn these four chain methods and you've replaced most of Eloquent:

{% set posts = craft.entries
    .section('blog')
    .status(null)              {# include disabled/drafts #}
    .with(['author', 'heroImage'])   {# eager-load, kills N+1 #}
    .orderBy('postDate DESC')
    .limit(12)
    .all() %}

Always .with() for relational fields rendered in a loop; this is the number one performance lever in Craft. Use .cache() or {% cache %} for expensive nav/footer queries.

Headless / API work → GraphQL before custom controllers. Create a schema in the CP, generate a token, and POST to /api:

{ entries(section: "blog", limit: 10) { title uri ... on blog_article_Entry { body } } }

Per 5.9, @parseRefs and @transform are now opt-in per schema (they closed an IDOR). If you're upgrading, audit every schema for expected transform behavior before shipping. For preview requests, pass X-Craft-Preview-Token with the x-craft-preview value from the preview URL.

Bespoke logic belongs in a module, not templates. Scaffold modules/site/, register it in config/app.php, and hook events:

use craft\elements\Entry;
use craft\events\ModelEvent;
use yii\base\Event;

Event::on(Entry::class, Entry::EVENT_BEFORE_SAVE, function (ModelEvent $e) {
    /** @var Entry $entry */
    if ($entry->section->handle === 'blog' && !$entry->authorId) {
        $entry->authorId = Craft::$app->getUser()->getId();
    }
});

Saving programmatically looks like this — and the boolean is not enough:

if (!Craft::$app->getElements()->saveElement($entry, true, true)) {
    throw new \Exception(json_encode($entry->getErrors()));
}

Console commands and jobs for anything slow. Image transforms, resave, and external API syncs all go through the queue; write a craft\base\Job and push it, then run php craft queue/listen locally and a supervised worker in prod.

Data seeding / content migrations. Use php craft entrify <type> to generate a craft\db\Migration that creates sample entries, so php craft up gives every dev a populated site. entrify in 5.9 no longer requires a group handle and auto-assigns new sections to Categories/Tags pages.

For Laravel developers specifically: map the concepts once and move on — Service providers → plugins/modules + config/app.php; Eloquent → craft\elements\Entry Active Record + craft\db\Query; Blade → Twig (no @include, use {% include %} / {% embed %}); Artisan → php craft; php artisan tinker → php craft tinker isn't standard, use Craft::dd() in a template or a one-off console command.

Gotchas and Tips

Licensing is not MIT. GitHub reports NOASSERTION because Craft ships under the Craft License (Solo free, Pro/Enterprise paid) — budget and legal review before you assume "open source." The Dependents: 0 figure is also a signal: this is an application, not a library, so there is no ecosystem of packages depending on it. Upgrades are whole-app upgrades.

allowAdminChanges => false will bite you. With it off, plugin installs, field edits, and project config writes throw. That's correct for prod, but it means schema changes must land as YAML + php craft up. Don't try to "just tweak a field" on production.

Compiled templates are cleared by up, not migrate. In 5.9 the cache-flush responsibility moved. If you see stale markup after a deploy that only ran migrate, run php craft clear-caches/compiled-templates.

.all() without .limit() on a large section is the classic memory-exhaustion bug. Prefer .limit()/pagination, and .with() for relations. Also remember the default query excludes disabled entries and drafts — .status(null) when you actually want them.

Saving an element silently fails. saveElement() returning false gives you no message; always inspect $element->getErrors(). Validation failures, permission failures, and required-field errors all surface there.

Search and indexes go stale. After changing field handles or adding a site, run php craft resave/entries and php craft index-assets (or the equivalent index utility). New sites in 5.9 can be added to entries via the slideout, but the propagated content still needs a resave.

Queue workers are mandatory. Image transforms, Generate image transform jobs, system emails, and propagateAll all queue. maxCachedCloudImageSize now defaults to 0, so cloud image caching behavior differs from earlier versions — verify transforms in staging.

Twig is a security boundary. 5.9 restricts create() to yii\base\BaseObject subclasses and adds an enableTwigSandbox config. If you let content editors author Twig (or you eval user input), turn the sandbox on. Never |raw untrusted content.

GraphQL schemas need a post-upgrade audit. Making @parseRefs/@transform per-schema optional changes behavior for existing schemas — either re-enable the directives explicitly or update clients. New uuid() and randomString() Twig functions and |hash('sha256') are handy for generating cache-busting keys and stable identifiers.

Debugging path. Set devMode => true locally to get the Yii debug toolbar and full stack traces; logs live in storage/logs/web.log and storage/logs/console.log. Craft::dd($var) from a module or console command is the fastest tinker replacement. For "why is this template rendering this?" use {% dump %} or the EVENT_BEFORE_RENDER_TEMPLATE hook — 5.9 lets you rewrite the template path there, which is a clean seam for theming overrides.

Composer hygiene. Since 5.9, package constraints use caret operators — keep them and pin to a minor. composer update craftcms/cms frequently requires coordinated plugin updates plus php craft up; do that on a branch, never as a hotfix on prod.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console