unite-cms/collection-field-bundle
A Unite CMS bundle that adds a collection field type for defining and managing structured collections of content directly within Unite CMS schemas, entries, and templates. It helps editors organize related items in a consistent, reusable way.
Install the bundle with Composer alongside the Unite CMS core packages, then register it in your kernel (or config/bundles.php on Symfony 4+):
composer require unite-cms/collection-field-bundle
// app/AppKernel.php — Symfony 3.x style
$bundles[] = new Unite\CollectionFieldBundle\UniteCollectionFieldBundle();
The bundle contributes a new field type (collection) to the CMS. It should be discovered automatically via the field-type compiler pass — if the type doesn't show up in the admin's field picker, check that the bundle is registered after the core Unite field bundle and that the container was cleared.
Where to look first, in this order:
src/ — it's the thing that defines the settings schema, the validation, and the persisted value shape. Everything else follows from it.Resources/views/ — the default Twig templates tell you exactly what the rendered value looks like (and what you'll be overriding).tests/ — the fixtures are usually the fastest way to see a complete, working content-type definition with nested fields.First use case: a repeatable "Team members" block on a Page content type.
# content type definition
title: Page
fields:
- title: { type: text, label: Title }
- team:
type: collection
label: Team members
settings:
fields:
- name: { type: text, label: Name }
- role: { type: text, label: Role }
- photo: { type: file, label: Photo }
Render it with a guard against empty collections:
{% if page.team is not empty %}
<ul>
{% for member in page.team %}
<li>{{ member.name }} — {{ member.role }}</li>
{% endfor %}
</ul>
{% endif %}
Nested field composition. The value of a collection is an ordered list of "rows", each row being a map of key → value using the same field types the CMS already ships (text, textarea, file, checkbox, and so on). The main pattern is composition: keep a small library of reusable collection definitions (a "card", a "link", a "stat") and drop them into multiple content types. Prefer many narrow collections over one wide, optional-everything collection.
Reuse via content type include/import. Rather than copy-pasting the same settings.fields block across content types, keep the shared collection definition in a dedicated YAML file and import it into each content type. This keeps the admin UI consistent and means a schema change happens in exactly one place.
Rendering overrides. Don't edit vendor templates. Copy the bundle's collection template into templates/bundles/UniteCollectionFieldBundle/ (Symfony's bundle override path) or, better, define a view on the field itself pointing at your own partial. Use one partial per collection shape so the markup for a "card" lives in a single file no matter which content type renders it.
Server-side access. From a controller or service, treat the field like any other: pull the content entity, get the field by name, and iterate. The value is a plain array of arrays — no proxy objects, no lazy loading — which makes it trivial to hand off to a serializer or a view model. This is the pattern to use when you need to transform collection rows before rendering (e.g. resolving file IDs to public URLs in a batch, rather than one at a time in Twig).
API/GraphQL exposure. Under Unite CMS v1 the collection field surfaces as a JSON array in the content API. Consumers should treat the array order as meaningful (it's the editor's drag-and-drop order) and treat missing keys as absent fields rather than empty strings.
Validation. Per-field constraints inside settings.fields are enforced through the normal CMS validation pipeline. Put required-ness and length limits on the nested fields themselves, not on the collection as a whole — a collection-level "required" usually means "at least one row", which is a different and blunter rule than most editors want.
Fixtures and tests. For test suites, build content through the same YAML content-type definition you use in production rather than hand-crafting JSON blobs; it keeps your tests honest about the schema and catches breakage when nested fields are renamed.
The value is stored as serialized JSON, not as rows. This is the single most important thing to internalize. You cannot WHERE on, sort by, or aggregate over nested field values in SQL. If a piece of data needs to be filtered, sorted, paginated, or joined — use a real Doctrine entity with a relation instead. Reserve collections for presentational repeats: hero slides, team members, feature lists, FAQ entries.
Renaming a nested field key silently orphans data. Because rows are keyed by field name, changing role to jobTitle in the YAML does not migrate existing rows; old values stay under the old key and disappear from the admin. Add the new field, write a one-off migration/backfill that rewrites the JSON, then remove the old field — never rename in place on a live site.
Rows have no identity. There is no per-row UUID unless you deliberately add one (a hidden text field populated on save, or a custom field type). If the frontend needs stable keys — for anchor links, analytics, or a JS store — add an explicit id or slug nested field and make editors fill it, or generate it in a prePersist listener.
Empty vs. null. An untouched collection may come back as null, an empty array, or an array of one empty row depending on how the form was submitted. Always guard with {% if page.team is not empty %} and, in PHP, is_array($value) ? $value : [] before iterating. Never assume the value is iterable.
Depth is tempting and expensive. Nesting a collection inside a collection works, but the admin UI degrades quickly and validation errors become hard to trace. Two levels is a practical ceiling; beyond that, model the relationship as separate content types.
File fields inside collections. Nested file/image fields participate in the CMS upload lifecycle, but the ordering of the upload relative to the content save is the usual source of "broken image after first save" bugs. Test the create-then-edit flow explicitly, not just the edit of an existing record.
Version pinning. This bundle belongs to the Unite CMS v1 generation (Symfony 3.4/4.x era). Pin it and the core field bundle together in composer.json and check the constraint resolution before upgrading Symfony — a loose constraint here will happily pull you into an incompatible core minor. Budget time for the eventual framework upgrade; treat this as legacy-adjacent infrastructure and isolate it behind your own view partials and view models so replacing it later is a rendering change, not a rewrite.
Debugging. When a collection renders nothing, dump the raw field value before suspecting the template: a surprising number of issues are a renamed key or a null that Twig silently skipped. After changing a content type definition, clear the container and CMS cache — stale field metadata is the second most common cause of "my new nested field doesn't appear".
Extension point. If you need behaviour the built-in nested types don't cover, write a custom field type and register it as an allowed child of the collection rather than forking this bundle. The collection field only needs to know how to nest and serialize; anything smarter belongs in a sibling field type.
How can I help you explore Laravel packages today?