orbitale/cms-bundle
Orbitale CMS Bundle is a lightweight, extensible content management bundle for Symfony. It provides pages, menus, and reusable content blocks with admin tooling, Twig integration, SEO-friendly URLs, and a flexible structure for building and managing multilingual websites.
Coming from Laravel, the first thing to internalize is that this is a Symfony bundle, not a Laravel package. There is no package auto-discovery, no service provider, no Blade, and no Eloquent. You will be working inside a Symfony application (or standing up a small one as a headless content service). Treat that as the baseline for everything below.
Version gate first. As of v4.1.0 the bundle requires Symfony 5.3 minimum (previously 5.0) and now supports Symfony 6.0. If your app is on Symfony 5.0–5.2 you cannot take 4.1.0 without upgrading Symfony first; pin 4.0.* if you are stuck. On a fresh Symfony 6 Flex app you can install the latest normally.
Minimal setup:
composer require orbitale/cms-bundle in a Symfony Flex app (Symfony 5.3+ or 6.0).config/bundles.php:
Orbitale\CmsBundle\OrbitaleCmsBundle::class => ['all' => true],
vendor/orbitale/cms-bundle/Resources/config/routing* for the exact file name) into config/routes.yaml:
orbitale_cms:
resource: '@OrbitaleCmsBundle/Resources/config/routing.yaml'
doctrine:schema:update --force in dev:
php bin/console doctrine:schema:update --force
Where to look first: the bundle's Page and Block entities, the BlockType base class (the extension point you will spend most of your time on), and the Twig extension that renders blocks. Then the admin controllers — that is your CRUD UI for free.
First use case: build one custom BlockType (a text block), create a page in the admin, attach the block, and render the page from a controller. That single loop — model page → compose blocks → render — is 90% of how the package earns its keep.
Block types are the unit of work. A block type is a small class that pairs a Symfony FormType (the authoring UI) with a Twig template (the front-end output). Register it as a tagged service or in the bundle config, give it a machine name, and you have a new content component:
class TextBlockType extends AbstractBlockType
{
public function getName(): string { return 'text'; }
public function getFormType(): string { return TextBlockFormType::class; }
public function getTemplate(): string { return 'blocks/text.html.twig'; }
}
Keep the block config to flat scalars (strings, ints, arrays of IDs). Anything structured in the form becomes structured in the database, and schema drift is painful later.
Page composition. Pages hold an ordered list of blocks. Rendering is a loop over page.blocks, delegating each block to its own template via the bundle's render helper (the Twig function exposed by the bundle's extension — orbitale_cms_render_block and friends, depending on version):
{% for block in page.blocks %}
{{ orbitale_cms_render_block(block) }}
{% endfor %}
A catch-all Symfony route (/{slug}) that loads a Page by slug and falls through to a 404 is the simplest front-end mapping.
Custom block types for domain data. The pattern that pays off is a block that stores IDs and renders via a repository — e.g. a "product carousel" block that saves a list of SKUs and resolves them at render time. This keeps editors in control of placement without duplicating product data in the CMS.
Headless mode (the realistic Laravel bridge). If the front end is Laravel and the content must live in the CMS, run the bundle as a separate Symfony service, expose pages/blocks as JSON from a thin controller (or API Platform), and consume it with an HTTP client on the Laravel side. Cache the JSON with a short TTL, invalidate on block save. Do not attempt to render Twig templates from Laravel. Given 4.1.0's Symfony 6 support, building this sidecar on a current Symfony LTS is now a realistic option rather than a compatibility gamble.
Admin reuse. The bundled admin gives you page/block CRUD. Extend it by overriding templates (templates/bundles/OrbitaleCmsBundle/...) and adding your custom block types — not by rewriting entities unless you have to.
Caching. Rendered pages are the obvious cache boundary. Cache the compiled HTML keyed by page id + updated timestamp, and hook a Doctrine lifecycle listener on Block/Page to purge. Without this, every block render is a set of queries.
php artisan vendor:publish, no facade. If your project is Laravel-only, the correct answer is usually "don't" — stand up a Symfony sidecar or pick a headless CMS. Trying to shoehorn Symfony bundle entities into Eloquent is a bad trade.composer update on 5.0–5.2 projects rather than at runtime. Decide explicitly: upgrade Symfony, or pin 4.0.* and accept you are off the supported path. There is no shim.composer.json, and keep the fork option open. Read composer.json constraints rather than trusting the release notes alone.vendor. Adding columns means entity inheritance, mapping overrides, or a fork. Decide early which of those you're signing up for.Block rows./admin, adjust the imported route prefix and your security firewall. The admin is protected by the app's firewall, not the bundle's.BlockType services are still autoconfigured, since tag/service defaults differ subtly across 5.3 → 6.0.BlockType services, Twig template overrides, Doctrine event listeners for cache invalidation, and a JSON controller for headless consumption. These four cover most day-to-day needs without touching the vendor code.Page.How can I help you explore Laravel packages today?