anomaly/repeater-field_type
Repeater field type for the Anomaly Streams platform. Adds a repeatable field that lets editors build multi-row sets of sub-fields, such as lists, feature grids, or FAQ entries, directly in the control panel, with each row saved as structured data on the entry.
This is a Streams Platform (PyroCMS 3.x) field type addon, not a standalone Laravel package. It gives a stream field a repeatable set of sub-fields stored as one serialized value — a lightweight "matrix"/"repeater" without a separate related stream. It is a small, single-purpose addon (1 star, no dependents), so treat it as an internal building block for a Streams project rather than a community-supported library.
Minimal setup inside an existing Streams/PyroCMS project:
composer require anomaly/repeater-field_type
php artisan addon:install anomaly.repeater_field_type
php artisan addon:publish anomaly.repeater_field_type
Then add the field to a stream in a migration (or via the admin Fields UI):
protected $fields = [
'specs' => [
'type' => 'anomaly.field_type.repeater',
'config' => [
'fields' => [
'label' => ['type' => 'anomaly.field_type.text'],
'value' => ['type' => 'anomaly.field_type.text'],
],
],
],
];
Look first at:
src/RepeaterFieldType.php — how config.fields is read and how the value is (de)serialized.resources/views/input.twig — the actual row markup and input naming convention.resources/config/config.php and resources/lang/en/addon.php — defaults and label keys.resources/js/*) — add/remove-row behavior and any events it emits.First use case: a page/module stream with a "specs", "links", or "faq" repeater — 3–10 rows of simple scalar fields, rendered in Twig with a for loop and edited inline in the admin form.
Define fields in config, not code. The whole shape of a repeater is the config.fields array. Keys become both the stored row keys and the labels (unless you supply config.label/lang keys), so name them like columns:
'config' => [
'fields' => [
'title' => ['type' => 'anomaly.field_type.text', 'config' => ['required' => true]],
'link' => ['type' => 'anomaly.field_type.url'],
'open' => ['type' => 'anomaly.field_type.boolean', 'config' => ['default_value' => false]],
],
],
Prefer the migration/config route over the admin UI: the field definition is then version-controlled with the stream, and code that reads $field->getConfig()['fields'] stays in sync with what the form renders.
Reading values in Twig. The stored value is an ordered list of associative rows, so iterate directly:
{% for spec in entry.specs %}
<dt>{{ spec.label }}</dt>
<dd>{{ spec.value }}</dd>
{% endfor %}
Because the rows are not real field-type presenters, nested values arrive as raw stored data — no automatic |nl2br, no presenter formatting, no relation expansion.
Reading values in PHP. Pull the raw array and normalize it yourself before doing anything with it:
$rows = $entry->specs; // array/collection of arrays — dump() before you trust the shape
foreach ($rows as $row) {
// $row['title'], $row['link'], $row['open']
}
If the addon's presenter decorates rows, property access may work on some versions and not others; a small array_map(fn ($r) => (array) $r, ...) on read is cheap insurance.
Writing values programmatically (seeders, imports, API endpoints). Set the field to a plain array of rows with matching keys, then save the entry:
$entry->fill([
'specs' => [
['label' => 'Weight', 'value' => '1.2 kg'],
['label' => 'Warranty', 'value' => '2 years'],
],
])->save();
Do not rely on the form layer for this; the form just collects the same array shape.
Building UI outside the default form. $field->getConfig()['fields'] gives you the sub-field definitions, so you can render read-only tables, generate CSV exports, or build an API response by mapping each row through the sub-field types you declared. That is the main "leverage" of this addon: one stream field, many ad-hoc table/list renderings.
Form reuse. The field can be added dynamically with $builder->add('specs', 'anomaly.field_type.repeater', $config) anywhere a Streams form builder is used, including custom admin controllers — useful for settings pages that need a variable-length list without a new stream.
text). A few dozen rows of short scalars are fine; hundreds of rows or long text will hit the 64KB MySQL TEXT limit. Use a dedicated related stream/module when the list is unbounded or large.required/array rules on the repeater field, or validate the array in a form request/validator on save.config.fields leaves old data under the old key. There is no migration for the inner rows — write a one-off script that walks entries and rewrites the array before you change key names.order sub-field and sort on read; drag-and-drop depends on whatever JS ships in resources/js.php artisan addon:publish anomaly.repeater_field_type and clear caches (php artisan view:clear, php artisan cache:clear). Check the browser console for 404s on the addon's JS/CSS.config.fields, rebuild the stream cache (e.g. php artisan streams:compile) or the form will render the old sub-fields.dump($entry->getFieldValue('specs')) versus $entry->specs — the raw value and the presenter may differ. When in doubt, read the raw column with DB::table(...)->value(...) and json_decode it.config.fields, a custom Twig partial that loops the value, and a custom presenter class if the addon exposes one. For anything more (custom row rendering, reordering UI, conditional sub-fields), expect to fork the addon or build a bespoke field type — it is a 1-star, 0-dependent package with no real maintenance signal, so pin the version and keep a fork ready.How can I help you explore Laravel packages today?