newebtime/repeatering-field_type
Repeatering Field Type is a Laravel/PHP field type for repeatable form inputs, letting you add, reorder, and manage multiple grouped values within a single field. It simplifies structured content editing in admin panels and Streams-style applications.
This is a PyroCMS stream field type — not a standalone Laravel package. It only makes sense inside a PyroCMS application that already has the Streams platform (anomaly/streams-platform) installed.
composer.json type of pyrocms-module/addon, but with zero stars and zero dependents the safest route is dropping it into addons/{vendor}/repeatering-field_type/ (or ./addons/default/... depending on your multi-site layout) and registering it.php artisan addon:install newebtime.field_type.repeatering
Then confirm the field type appears under Addons → Field Types in the control panel.src/RepeateringFieldType.php — the FieldType class; check getInputView(), getOutputView(), getWrapperView(), and getConfigurationView() to see how it wires up sub-fields.resources/views/ — the input partial used in forms and the output partial used in templates.resources/lang/ — labels and config text.src/RepeateringFieldTypePresenter.php (if present) — the presenter is what you actually call from templates, so read this before writing any markup.pages stream) with type Repeatering, then define one or more sub-fields (label + value pairs, or sub-field slugs) in the field's configuration. The canonical use case is a page with a variable list of rows — FAQs, feature cards, footer links, timeline entries — where you don't want a whole separate stream per row.Validate with a trivial page first: add one text sub-field, save two rows, and dump the raw value in a Blade/Twig view to see the storage shape before building anything real.
Field configuration drives the schema. Unlike a simple text field, a repeater's behaviour is defined by its field config (the sub-fields/columns). Treat that config as a mini-migration: it is serialized and stored per-field, so changing it later does not reshape existing data. Decide your sub-fields up front, or write a data-normalisation routine when you change them.
Reading values in templates. The field returns a collection of row objects/arrays. Typical access looks like:
@foreach($entry->faq_items as $item)
<dt>{{ $item->question }}</dt>
<dd>{{ $item->answer }}</dd>
@endforeach
If $item is a plain array you'll need $item['question']. Check the presenter's __get/offsetGet to know which you get — it varies between field types. Dump $entry->faq_items once in a dev view and never guess again.
Writing values programmatically. When seeding or mutating from a service, build the payload as an array of associative arrays keyed by sub-field slug, then save the entry through the normal Streams repository ($entries->save($entry)). Don't write raw JSON into the column unless you also handle the field type's expected encoding — round-trip through the model so the field type runs its transformation.
Pairing with stream forms. Because the input view renders its own repeatable row markup, it composes well inside a form field type. If you build custom CP UI, reuse the addon's own input partial rather than reimplementing row add/remove, otherwise your markup will drift from the field's expected input payload (field_slug[row_index][sub_field_slug] style keys).
Common integrations:
Migration pattern for new fields:
$field = $fields->create([
'slug' => 'faq_items',
'type' => 'anomaly.field_type.repeatering',
'name' => 'FAQ Items',
'config' => [
// sub-field definitions per the addon's config schema
],
]);
$assignment = $assignments->create([...]);
Storage is opaque. Repeater values are almost certainly stored as JSON (or a serialized blob) in a single text/longtext column. That means: no SQL search across sub-fields, no indexing, no aggregate queries, and WHERE clauses are useless. If you need to search or sort by a sub-field, promote it to a real column or a related stream.
Size limits bite. MySQL TEXT caps at 64KB. A repeater with many rows and long fields will silently truncate or error. Confirm the migration uses longText, and if you control the migration, make it one.
Config changes don't migrate data. Adding/removing/renaming a sub-field leaves old rows with stale keys. Write a one-off artisan command to walk entries and normalise. There's no built-in versioning.
Nesting is risky. Do not assume the repeater can contain another repeater. Check the input view's row template — if it only renders scalar field types, nesting will either fail or produce unusable input names. For nested structures, use two levels of streams/relationships instead.
Validation is per-field, not per-sub-field. Required/validation rules on sub-fields may not surface as form validation errors. Add server-side validation in your controller or a form request handler, and remember to sanitise before rendering (sub-field values are user input).
Debugging checklist: dump the raw model attribute ($entry->getAttribute('faq_items')) versus the presenter access ($entry->faq_items). Half the "it's empty" reports are a presenter returning a Collection you're not iterating, or an entry not being re-fetched after save.
Extension points:
RepeateringFieldType and register your own field type if you need custom validation or a different storage format.__call/accessor overrides for custom output helpers (e.g. a toUl() method).Bus factor is 1. Zero stars, zero dependents, single vendor on GitLab. Pin the exact commit in your composer.json (or vendor the addon into addons/), and add a smoke test that saves and re-reads a two-row entry so an accidental upgrade doesn't quietly break production content. If the addon is abandoned, the migration path is: read the JSON column directly, normalise to a dedicated stream, and drop the field.
How can I help you explore Laravel packages today?