eminiarts/aura-cms
Aura CMS is a Laravel content management system built on the TALL stack. Define content types as PHP classes and Aura generates the admin panel around them, complete with forms, tables, search, media, and permissions.
Install into a Laravel 12/13 app on PHP 8.4+. The README is explicit that Aura 1.0 is a beta channel and that plain composer require will silently resolve to the old 0.2.x line. Pin the channel first:
composer require eminiarts/aura-cms:^1.0@beta
php artisan aura:install
The installer is interactive: it publishes config and assets, points Aura at your App\Models\User, runs package migrations, and creates the first Global Admin. Check php artisan aura:install-config --help for the non-interactive flags if you're scripting CI or a fresh clone — a seedable admin beats clicking through the wizard every time you rebuild a dev box.
Where to look first. After login, /admin is the whole product surface. Before writing a line of code, read the field reference at aura-cms.com/docs/fields — it's the API you'll spend 90% of your time in. The docs are LLM-friendly (llms.txt, and any page as raw markdown by appending .md), which is genuinely useful when you're pair-programming with an assistant on a form layout.
First use case: one content type, end to end. Scaffold rather than hand-write:
php artisan aura:resource Article
A resource is just an Eloquent model declaring its fields:
namespace App\Aura\Resources;
use Aura\Base\Resource;
class Article extends Resource
{
public static string $type = 'Article';
public static ?string $slug = 'article';
public static function getFields()
{
return [
['type' => 'Aura\\Base\\Fields\\Text', 'name' => 'Title', 'slug' => 'title', 'validation' => 'required|max:255'],
['type' => 'Aura\\Base\\Fields\\Wysiwyg', 'name' => 'Content', 'slug' => 'content'],
['type' => 'Aura\\Base\\Fields\\BelongsTo', 'name' => 'Author', 'slug' => 'author_id',
'resource' => 'Aura\\Base\\Resources\\User'],
];
}
}
Drop it in app/Aura/Resources and /admin/article immediately serves a sorted, searchable, filterable index plus create/edit forms with your validation. No migration. Fields land in meta storage, so you can iterate on the shape of a content type in a meeting and have it live before the meeting ends. That feedback loop is the reason to pick this package over hand-rolling Livewire CRUD.
Model fields as a first-class deliverable. Treat getFields() as the schema of record and let Aura own the UI. When a stakeholder asks for "one more field," you edit one array entry, not a migration + form request + Blade view + table column. Batch field changes into a single commit per content type so the diff reads as a schema change.
Move to a real table deliberately, not early. Meta storage is the default and it's fine for editorial content. Promote a resource to its own table when you need indexes on hot query paths, joins from other application code, or high row counts. Ship the migration yourself and keep getFields() intact — the admin panel doesn't care where the data lives.
Use aura:database-to-resources for legacy apps. If you're bolting Aura onto an existing database, generate resources from the tables and then edit them down. Much faster than transcribing columns into field arrays by hand.
Permissions belong in policies. Aura enforces per-resource view / create / edit / delete / restore / forceDelete through Laravel policies. Generate the permission scaffolding with aura:create-resource-permissions, then put the real business logic in the policy class. Don't scatter can() checks through custom views — the policy is the single source of truth and the admin UI reads it automatically.
Choose the table layout per resource, then save views. List, grid, and kanban are all available; saved filters and per-user column settings live on top. This is the feature your editors will actually notice day-to-day — set up the default filters for them rather than making each person configure their own.
Extend with aura:field before you fork. A custom field is a small class with a render concern; the 42 built-ins cover most needs, so reach for a custom field only when the data shape is genuinely new (e.g. a domain-specific picker). Same for aura:plugin — package your resources, fields, and pages as a plugin instead of copy-pasting resource classes between client projects. If you're an agency shipping the same CMS to several clients, this is the highest-leverage investment in the package.
Customize components, don't override them. aura:customize-component and aura:publish give you the escape hatch for the one view that doesn't fit. Prefer publishing a single component over editing vendor files, so package upgrades stay a composer update.
Testing. Aura is a container singleton with request-scoped state. In Pest tests, call Aura::flushState() in teardown (or use the package's test helpers) or you'll get leaked state between tests — the package's own suite hit this, so your app suite will too. Also remember team scoping: a Resource::query() in a test may return fewer rows than you expect when teams are enabled.
The version matrix in the docs contradicts itself. README says PHP 8.4+/Laravel 12/13/Livewire 4; agents/AGENTS.md says PHP 8.2+/Laravel 10–12. Trust the README and the changelog (beta.4 raised the floor), and pin your app's constraints before installing rather than discovering the mismatch mid-install.
Beta churn is real. There is no stable v1.0.0 tag. Beta.4 alone decomposed a "god-trait," added DefinesFields, changed migration rollback ownership, and altered meta query portability. Pin the exact beta in composer.json, read each changelog before bumping, and budget time for it. Don't run this on a client project with a fixed deadline unless you're prepared to hold a version.
Upgrading from 0.x is a rebuild, not an upgrade. The 1.0 line is a fresh baseline that removed things you may depend on (the old AuraModelConfig trait was decomposed). Follow UPGRADING.md; do not assume composer update is survivable.
Teams introduce a global scope. TeamScope filters every resource query automatically. Your own code, seeders, console commands, and tests must either set a current team or explicitly withoutGlobalScope() when they need cross-team reads. This is the #1 source of "why is my query returning nothing." Related: permission and scope caches are keyed per user/team — stale caches after changing a user's team cause phantom access issues, so clear them when mutating membership.
Config quirks worth knowing on day one. AURA_CREATE_TEAMS / create_teams controls team auto-creation. aura.features.legacy_fields_append appends a computed fields attribute to every model serialization — set it to false on new apps or your API payloads will quietly balloon.
Meta querying is portable but not free. whereMetaContains was fixed to use portable JSON containment (previously MySQL-only behavior). It works everywhere now, but filtering on meta columns is still slower than a real column — another reason not to postpone the table migration forever.
Media uploads got hardened in 0.2.0. Don't assume permissive defaults from older blog posts; validate mime types and sizes in the field config and re-publish assets after upgrades (aura:publish) or you'll be debugging stale JS.
Octane and queue workers. Aura flushes its request-scoped state on Octane's request/task/tick events — but only if you're on a version that includes it. In long-running workers and in tests, call Aura::flushState() yourself.
The Visual Resource Editor is local-only and super-admin-only. It writes field changes back into your PHP classes — brilliant while scaffolding, dangerous in production, which is why it's gated. Use it to build the first draft of a big form, then review the generated PHP in your editor and commit it like any other code.
Rename field slugs carefully. Meta-stored data is keyed by slug; changing slug orphans existing values silently. Write a backfill when you rename, or accept the data loss knowingly.
Performance in tables. Recent betas batch BelongsTo/Image column resolution per page and eager-load Tags/AdvancedSelect. If you're on an older build and your index page does N+1, that's the cause — implement PreloadsTableDisplay / ProvidesTableEagerLoad on the resource, or upgrade.
Dependents: 0 and stars: 0. This is a small, agency-driven package with no public production track record you can audit. That's not disqualifying — but it means you own the debugging. Keep a fork, read the source when behavior surprises you, and prefer the documented extension points over monkey-patching.
How can I help you explore Laravel packages today?