statamic/cms
Statamic is the flat-first, Laravel + Git powered CMS for building beautiful, easy to manage websites. This package is the core Composer library installed into an existing Laravel app, offering extensive documentation, community support, and official Pro support.
Statamic is a flat-file CMS that lives inside a normal Laravel app. This repo is the core package, not a starter app — so there are two entry points:
statamic/statamic) via the Statamic CLI, which pre-wires routes, config, and a default CP.composer require statamic/cms, then run the installer and publish config.composer require statamic/cms
php please install # or: php artisan statamic:install
Set at minimum in .env:
APP_URL=https://mysite.test
STATAMIC_LICENSE_KEY= # required for Pro features
Where to look first (in order):
config/statamic/ — stache.php, sites.php, static_caching.php, assets.php, repositories.php, api.php.content/ — everything an editor creates: content/collections/{collection}/{slug}.md with YAML front matter, content/trees/, content/globals/, content/taxonomies/.resources/blueprints/ — the YAML field definitions that drive both the CP and validation.resources/views/ — Antlers (.antlers.html) or Blade (.blade.php) templates.routes/web.php — Statamic registers front-end routes from your collections and any Route::statamic() calls.src/Entries, src/Fields, src/Tags, src/Http/Controllers/CP. Reading these is how you answer "can I do X?" faster than the docs.First real use case — a blog:
# resources/blueprints/collections/blog/post.yaml
sections:
main:
fields:
- handle: title
field: { type: text, required: true }
- handle: content
field: { type: bard, validate: required }
// routes/web.php
Route::statamic('blog/{slug}', 'blog.show', ['layout' => 'layouts.blog']);
{{# resources/views/blog/index.antlers.html #}}
{{ collection:blog sort="date:desc" limit="10" paginate="true" as="posts" }}
{{ posts }}
<a href="{{ url }}">{{ title }}</a>
{{ /posts }}
{{ /collection:blog }}
{{ paginate }}{{ links }}{{ /paginate }}
That loop — collection tag, blueprint-driven fields, URL from the entry — is the daily heartbeat of Statamic work.
Querying content from PHP (not templates). The facades are the standard integration point with the rest of your Laravel code:
use Statamic\Facades\Entry;
$posts = Entry::query()
->where('collection', 'blog')
->whereStatus('published') // 6.9 fixed this; prefer it over raw `published` checks
->orderByDesc('date')
->limit(5)
->get();
$entry = Entry::findByUri('/blog/hello-world'); // respects multisite + routing
Use this in controllers, API resources, sitemaps, mailables, Livewire components — anywhere Antlers is the wrong tool.
Antlers inside Blade. You rarely need to choose one template engine exclusively:
{{-- renders a tag and returns its string output --}}
{!! Statamic::tag('collection')->from('blog')->limit(5) !!}
Keep Antlers for content-shaped markup (loops over entries, conditionals on fields) and Blade for app layout, components, and anything with PHP logic.
Content modeling. Blueprints are the schema. Extract shared groups into resources/fieldsets/*.yaml and reuse via field: { type: group, fields: my_fieldset } or import. Sets in Bard/Replicator are where most editorial flexibility lives — define them once in a fieldset and reference them.
Events are the clean integration seam. Core fires Statamic\Events\* on every write (EntrySaved, EntryCreated, EntryDeleted, AssetSaved, FormSubmitted, UserSaved, …). Register listeners the Laravel way (in Laravel 11+ that's usually Event::listen() in a service provider or auto-discovery):
Event::listen(function (EntrySaved $event) {
Cache::forget("entry:{$event->entry->id()}");
// dispatch a job, ping a webhook, reindex search…
});
Prefer events over patching core or hooking repositories.
Building on top of it. Addons are the sanctioned extension path, and the generators are excellent:
php please make:addon vendor/thing
php please make:tag # custom Antlers tag
php please make:modifier # Antlers modifier
php please make:fieldtype # PHP class + Vue CP component
php please make:widget
Fieldtypes are two-sided: a PHP class exposing render()/augmentation and a Vue component in the addon's resources/js. That's how most "we need a special editing experience" tickets get solved.
Forms and validation. Forms live in resources/forms/*.yaml with per-field validate rules; submissions are stored as files and exposed via Statamic\Facades\Form. Prefer a listener on FormSubmitted for CRM/email work over hacking the form controller, and configure honeypot/CSRF rather than writing your own.
Caching is the performance story. Two layers, both worth understanding early:
// config/statamic/static_caching.php → strategy: 'full' | 'half' | null
php please static:warm
php please static:clear
Plus the in-template {{ cache }} tag for expensive partials. Static caching is the difference between a 40ms and 400ms page; wire it into your deploy script from day one.
Delivery APIs. REST is built in (/api/collections/blog/entries, config in config/statamic/api.php); GraphQL is Pro. Both respect the same blueprints, so headless front-ends get field names for free. For decoupled setups, this plus static caching is usually the whole architecture.
Multi-site. config/statamic/sites.php defines locales/domains; entries localize automatically. Use Entry::query()->where('site', $site) and {{ site:handle }} in templates. Test in at least two sites before shipping.
The Git workflow. Content is files, so content/ belongs in the repo (or a synced content repo). Day-to-day: editors work in the CP, developers review content diffs in PRs, and CI runs php please stache:clear + static:warm on deploy.
storage/statamic/. If files change outside the CP (git pull, deploy, script writing markdown), indexes go stale. In dev, keep the watcher on; in production, always run php please stache:clear on deploy and consider warming it. php please stache:doctor diagnoses index weirdness.id, slug, date, published, updated_at, locale, origin, and anything _-prefixed are Statamic-owned. Name your fields around them — collisions produce confusing, silent behavior.date drives status and ordering. published: true plus a future date means scheduled, not visible. Use whereStatus() rather than hand-rolling published + date comparisons (the 6.9 changelog shows how easy that logic is to get subtly wrong).title: Ship it: v2 breaks. Quote values containing : or starting with special characters, and prefer the CP/php please for content creation so formatting stays canonical.php please glide:clear; after moving containers, php please assets:.../reindex. Broken thumbnails are almost always a stale Glide cache or a changed container path.LICENSE.md), not MIT — read it before assuming redistribution rights. Several capabilities (GraphQL, multi-site beyond limits, revisions) are Pro and gated by STATAMIC_LICENSE_KEY. Check the pricing page before designing around a feature.storage/statamic. Treat it as disposable. If you must, stop, clear, write the file, re-warm.content/. Use Statamic's PreventsSavingStacheItemsToDisk testing concern, or point the app at a fixture content root in the test env.php please support:details (now scrubbed of app name/URL) for bug reports, php artisan about for the environment, verbose logs in storage/logs, and the Statamic:: facade in Tinker for "why is this entry not appearing?" — Entry::findByUri() returning null tells you it's routing/status, not templating.How can I help you explore Laravel packages today?