typicms/base
TypiCMS Base is the core foundation for TypiCMS, a modular, multilingual content management system built on Laravel. It provides shared CMS infrastructure, localization, and module support for building flexible, maintainable websites.
TypiCMS is a modular CMS, and typicms/base is the core package that every site depends on. You rarely install typicms/base into an existing Laravel app by hand — the normal entry point is the project skeleton (composer create-project typicms/typicms) which pulls in the base package alongside a set of first-party modules (pages, news, events, projects, galleries, contacts, blocks).
Minimal boot sequence:
composer create-project typicms/typicms my-site
cd my-site
cp .env.example .env
php artisan key:generate
# configure DB_*, then:
php artisan typicms:install # publishes config, runs migrations, seeds roles/admin user
php artisan storage:link
php artisan serve
The install command is the single most important thing to run — without it you get a bare Laravel app with a config/typicms.php but no tables, no admin role, and no seeded admin account. Log in at /admin with the seeded user.
Where to look first, in this order:
config/typicms.php — the contract for the whole system. The keys you will touch daily are locales (a code => label map), fallback_locale, template (which Blade layout/theme the public side renders), per_page, and the module list that decides which modules are actually mounted.Modules/ (or vendor/typicms/* if not yet published) — each module is a self-contained mini-app: Models/, Repositories/, Http/Controllers/{Admin,Public}, Http/Requests, resources/views/{admin,public} and its own routes.php / admin.php.TypiCMS\Modules\Core\Models\Traits — HasTranslations, Sluggable, Publishable, Positionable, HasMedias, HasFiles, Tag. Almost every "how do I make my model behave like a TypiCMS model" question is answered here.routes/web.php in the skeleton, plus the TypiCMS facade — the frontend router and the TypiCMS::pages() / TypiCMS::menus() helpers.First use case: create a Page in /admin, then render it. TypiCMS deliberately keeps the public rendering in your Blade files, so a first win is dropping this into resources/views/default/page.blade.php:
<article>
<h1>{{ $page->title }}</h1>
{!! $page->present()->body !!}
</article>
That single line teaches you the two central conventions: translated attributes are accessed as plain properties (locale-resolved), and rich content is rendered through a presenter, not echoed raw.
The module is the unit of work. Every feature in TypiCMS is a module, and the framework's conventions only make sense once you internalise the anatomy:
Modules/News/
├── Models/News.php
├── Models/NewsTranslation.php
├── Repositories/NewsRepository.php # interface + Eloquent impl
├── Http/Controllers/Admin/NewsController.php
├── Http/Controllers/Public/NewsController.php
├── Http/Requests/NewsRequest.php
├── resources/views/admin/{index,form}.blade.php
├── resources/views/public/{index,show}.blade.php
├── routes.php # frontend routes
├── admin.php # admin routes (auto-prefixed)
└── Providers/NewsServiceProvider.php
When you need a custom content type (say, "Jobs"), do not invent a parallel structure. Duplicate the News module, rename namespaces/tables/routes, register it in config/typicms.php, and you inherit admin CRUD, translation UI, media attachment, publishing, positioning, and search indexing for free. This is the single highest-leverage pattern in the package.
Multilingual content is table-per-model. Translations live in a sibling *_translations table; the model declares public $translatedAttributes = ['title', 'slug', 'body', 'status'] and uses HasTranslations. Day-to-day this means:
$news->title; // current locale, with fallback
$news->translate('fr')->title; // explicit locale
$news->getTranslation('body', 'en'); // without mutating current locale
$news->saveTranslation(['title' => '...'], 'fr');
URLs are locale-scoped and slugged per locale, so route definitions go through the TypiCMS:: helpers rather than hardcoded paths — /fr/nouvelles/mon-article and /en/news/my-article resolve to the same row.
Thin controllers, fat repositories. Admin controllers extend BaseAdminController and mostly delegate. If you need custom filtering, override $repository->getById() or add a query scope on the model — pushing query logic into the controller breaks pagination, search, and the JSON/API responses the admin grid expects.
Publishing and scheduling is a trait, not a boolean. Add the Publishable trait + status, start_date, end_date columns and TypiCMS handles "draft / published / scheduled" in both the admin UI and all public queries. You never need to filter where('status', 'published') yourself on the public side.
Media and files. HasMedias / HasFiles give you polymorphic attachments. Attach in admin, read in Blade:
$model->medias; // collection, ordered by position
$model->image(); // first media as an image helper/URL
$model->files; // downloadable documents
Menus, blocks and settings are shared services. Instead of hardcoding navigation, seed a Menu and render it:
@foreach (TypiCMS::menus() as $menu)
@include('core::public._menu', ['menu' => $menu])
@endforeach
Settings similarly: setting('site_title') or via the facade, cached per request and cleared on save.
Integration tips.
php artisan vendor:publish --tag=views (check --tag names per module). TypiCMS resolves Modules/Foo/resources/views before the vendor copy, so overriding is a matter of creating the file in the right place.$translatedAttributes (or $fillable for non-translated), and the admin form partial. Miss the second and the field silently never persists.$model->toArray() — translations are included automatically when $translatedAttributes is set.Module registration is config, not filesystem. A module folder sitting on disk does nothing until it appears in config/typicms.php (and in composer.json autoload/modules_statuses for module-manager based versions). Symptom: "my routes return 404 but the model works in tinker."
Config and route caching will bite you. After editing config/typicms.php, adding a module, or changing locales, run:
php artisan config:clear && php artisan route:clear && php artisan view:clear
php artisan cache:clear
The response cache (keyed by URL + locale) and the search index both survive a simple page refresh, so a change that "isn't showing up" is almost always a cache, not a bug.
Slugs don't regenerate on rename. TypiCMS generates the slug once from the title when it's empty. Renaming a published page leaves the old URL intact — usually correct, occasionally infuriating. Override generateSlug() on the model if you need different behaviour, and be aware per-locale slugs mean a rename in English doesn't affect the French URL.
Fallback locale is silent. With fallback_locale set, a missing French translation renders the English text rather than an empty string. This is great for launch, bad for QA — check $model->hasTranslation('fr') or $model->translation('fr') when you need to distinguish.
Migrations are published, not versioned per-app. typicms:install copies package migrations into database/migrations. Re-running it or upgrading the base package can produce duplicate table errors. On upgrades: diff the published migrations against the vendor ones, don't blindly migrate:fresh in anything resembling production.
Uploads need the storage symlink. Media and file fields will "save" but render broken images if php artisan storage:link hasn't run, or if FILESYSTEM_DISK differs from what the HasMedias trait expects. Also check config/filesystems.php public disk URL — TypiCMS historically assumed /storage.
Admin access is role-gated. A user exists but sees a 403 on /admin: check the role/permission assignment, not the auth guard. The seeded admin is the only superuser — creating a second admin from tinker requires attaching the role explicitly.
Debugging tips.
TypiCMS::getPage() / TypiCMS::pages() are the fastest way to inspect what the router resolved on the public side.BaseRepository calls — the biggest N+1 source is translations and medias. Eager load with ->with('translations', 'medias').*Request class rules() and the form partial — the base form builder renders from the request's field map in some versions.Extension points, ranked by safety:
vendor/typicms/* (don't; the next composer update erases it).Version pinning matters more than usual here. Releases are infrequent, and the base package's Laravel constraint moves with them. Pin typicms/base and all typicms/* modules to compatible versions together, and read the changelog before a minor bump — module/base mismatches are the most common source of "works in staging, white screen in prod."
How can I help you explore Laravel packages today?