manuxi/sulu-event-bundle
Sulu Event Bundle adds event management to Sulu CMS, letting editors create and organize events with dates, locations, and details, then display them through flexible templates. Integrates cleanly with Sulu’s admin and frontend.
This is still a Symfony/Sulu bundle, not a Laravel package — no artisan, no Eloquent, no Blade, no config/*.php service providers. It's Composer + Symfony bundles + Doctrine + Twig + YAML/XML config. The service container, facades-free DI, and bin/console will feel familiar; everything else is Sulu-flavoured.
Minimal path to a running feature:
composer require manuxi/sulu-event-bundle — check composer.json for the Sulu major it supports (^2.x) and the PHP floor; a Sulu 3 project will not accept it. Recent releases have moved quickly (v3.1.6 → v3.5.0 in a short window) — pin the minor you tested.config/bundles.php in Flex-style projects, or registerBundles() in src/Kernel.php). Sulu projects have two kernels — AdminKernel and WebsiteKernel — and this bundle needs to be in both (admin UI and website rendering).bin/console sulu:events:clean-image-data --dry-run, review the output, then re-run without --dry-run. It strips bloated image data that older releases wrote into the event's templateData — skipping this leaves dirty data in your DB and, per the release notes, can interact badly with the new "full media object" resolution.Resources/config/routing*.xml for the exact route names.bin/console doctrine:migrations:migrate (or doctrine:schema:update --force in dev). The bundle ships Event and Location entities, and Sulu entities carry extra columns for dimensions, locale, and audit fields.sulu.event.* contexts to your role (names live in Admin/*Admin.php::getSecurityContexts()). Without this the list views load empty and no error appears. This trips up every Laravel dev once.bin/console cache:clear and clear the Sulu admin cache if admin JS/metadata changed.Where to look first:
src/Admin/ — *Admin.php classes expose exact security contexts, list-view keys, form XML paths, and navigation entries.src/Entity/ — the real schema: fields, translatable properties, Event↔Location associations.src/Content/ (or SmartContent/) — the smart content data provider; the primary integration surface.src/Command/ — the sulu:events:* commands, including clean-image-data and the recurring-events command.src/Mcp/ (or wherever the MCP tool definitions live — grep for mcp) — new in this release, the "agent/automation" surface.Resources/config/forms/*.xml and lists/*.xml — the admin UI is defined in XML, not PHP. This is the Sulu equivalent of Laravel Nova resources.End-to-end smoke test: create a Location, create an Event tied to it with start/end dates, publish it (the list now shows a publish-state indicator — verify it flips), drop the event smart content into a page template, confirm it renders. Add an editorial tag or category to the event and confirm the smart content filter honours it — that's a new capability in this release.
Admin CRUD comes for free; extend the XML, not the PHP. Sulu generates list and form UIs from XML metadata. To add a field (say registration_url), add the column to the Doctrine entity + migration, then add a <property> to the bundle's form XML — but override the file in your project (config/forms/ with a matching name, or a sulu_admin resource override) rather than editing vendor/. Editing vendor works until the next composer update.
Smart content is the primary integration point — now with category/tag evaluation. In a page template XML:
<property name="events" type="smart_content">
<meta>
<title lang="en">Events</title>
</meta>
<params>
<param name="provider" value="events"/>
<param name="max_per_page" value="10"/>
</params>
</property>
As of this release the provider evaluates website categories and tags, so editors can build "next 10 talks in the Conference category" without a custom provider. This is the Sulu analogue of a Nova relationship field or a Laravel "block" and it's the reason to install the bundle. Confirm in the admin sidebar that category/tag filters appear — if they don't, you're on a cached admin or a stale template definition.
Decorate the provider for anything bespoke. For "only upcoming", "only at this location", or business rules the smart content params don't model, wrap the bundle's provider with decorates: in services.yaml. You inherit the bundled filtering/search/pagination plus the new category/tag handling. This is the pattern to reach for before forking.
Programmatic creation for imports and seeders. No Eloquent factory — use the Doctrine EntityManager from a console command or Symfony fixture. Two Sulu-specific rules: set locale/dimension explicitly, and set creator/changer to a UserInterface (Sulu audit traits) or the list shows blanks. flush() per N records, not per record. Two recent fixes make this safer: start/end dates are now normalized before persisting (so passing DateTimeImmutable, DateTime, or a partial "date only" string should land in UTC consistently), and referenced locations serialize as plain arrays rather than raw entity references — check any custom serializers you wrote against the old shape.
MCP tools — new, and useful even if you aren't building an agent. The bundle now exposes MCP tools for events and locations. The event-update tool is field-scoped: it only changes the fields you pass, so partial updates are safe. Two practical uses:
For the event-selection payload, ids are now UUID strings (previously sometimes treated as objects) — normalize on the consumer side if you have your own integration reading the same shape.
Automation bundle integration (optional). If sulu/automation-bundle is installed, the bundle now participates in scheduled publish/unpublish, so editors can set "publish Tuesday 9am, unpublish after the last session." The integration is guarded with interface_exists(), so it silently no-ops when the automation bundle isn't present — no dependency to add unless you want the feature.
Website rendering. Sulu does not do implicit routing. The bundle's controllers render Twig from templates/bundles/SuluEventBundle/... — override those files to restyle, don't copy the controller. The new direct routes for event templates mean you can link /events/{slug} from a listing without a controller wrapper; verify the slugs match your content-type convention. For a calendar view, server-render the shell with Twig, then hydrate FullCalendar from a JSON endpoint — either the new direct route or a small JsonResponse controller wrapping the bundle's repository.
Single-event content is now locale-aware. Recent fixes skip events with no content in the requested locale and populate webspaces on website search documents. If you build a "related event" or "next event" widget, expect an empty result instead of a fallback-locale rendering — handle that explicitly in Twig rather than letting a blank block ship.
Reuse over rebuild for the list API. Admin list endpoints are wired to Sulu's ListBuilder/DoctrineListBuilder. If you need events in a headless context, hit the admin endpoint (or the MCP tool) rather than writing a parallel API — you inherit filtering, sorting, and pagination the editors already understand.
bin/console sulu:events:clean-image-data --dry-run lists what will change; without it the command mutates rows. This is the only explicit upgrade note in the release and skips quietly if you treat the minor bump as routine.image, pdf, speaker, author in templateData, and the event image is no longer written into templateData (it lives on the proper media field and is resolved as a full media object). If your Twig reads content.templateData.image, migrate to the resolved media field. If you relied on "saving a form wipes the fields I didn't submit", that behaviour is gone.sulu_admin.smart_content_label override. If your project or another bundle depends on that key being globally set, re-add it explicitly. This is the closest thing to a breaking change in the release; audit custom admin translations.instanceof or ->getId() against the old selection payload needs a look. Same for locations now serialized as plain arrays rather than entity references.workflowPlace check was removed for live-stage event content, so an event that's published may appear even if the editor thinks it isn't. Confirm the publish-state indicator in the list before debugging anything else — it's the fastest signal.sulu.event.*, or the admin cache holding stale metadata. Role first, then cache:clear (and the Sulu admin cache clear).<property> changes, run bin/console cache:clear and the admin cache-clear command. A stale cache silently ignores your new field — this also masks the new category/tag smart-content params, making you think the feature isn't shipping.$model->save(), no mass assignment, no soft deletes by default. persist() + flush(). flush() in a loop is the classic write-N+1. Run doctrine:schema:validate before every release.workflowPlace guard on live content — but the recurring-events command now needs a content workflow. If you've customized or scheduled the recurring command, pass through the workflow argument the bundle expects; a missing workflow silently produces events in the wrong stage. Recurrence does exist in this bundle now (see the recurring-events command) — the old "check before you promise it" caveat is obsolete; instead, test at least one recurring series against the new date-normalization logic.vendor/. Override forms, lists, Twig templates in your project. The bundle is small enough that you'll be tempted; the release cadence (four minors in a short window) makes vendor edits especially short-lived.bin/console debug:container | grep event, doctrine:schema:validate, router:debug (and check the new direct event-template routes here), sulu:events:clean-image-data --dry-run to inspect image-data health, bin/console debug:messenger if you're using the automation bundle, and the Symfony Profiler's Doctrine panel on calendar pages.How can I help you explore Laravel packages today?