Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

List Field Type Laravel Package

emange/list-field_type

A Laravel/PHP field type add-on for the Streams Platform that adds configurable list fields to forms and models. It makes it easy to define, store, validate, and render ordered options or values in PyroCMS/Streams-based applications, with a lightweight setup and straightforward Composer installation...

View on GitHub
Deep Wiki
Context7

Getting Started

This is a PyroCMS 3.x / Streams Platform field type addon — not a standalone Laravel package. It only makes sense inside an application that already requires anomaly/streams-platform. Its job is to give you a repeatable array of text or textarea inputs on a stream entry without creating a second stream and a relationship just to hold "features", "specs", "bullet points", or "FAQs".

1. Pull it in via a VCS repository (it is not on Packagist):

"repositories": [
    { "type": "vcs", "url": "https://bitbucket.org/edsters/list-field_type.git" }
],
"require": {
    "emange/list-field_type": "dev-master"
}
composer update emange/list-field_type
composer dump-autoload
php artisan addon:install emange.list_field_type
php artisan addon:list        # confirm it shows as installed + enabled

2. Read these files first, in this order:

File Why
composer.json Confirm "type": "streams-addon", the PSR-4 map (Emange\ListFieldType\ → src/), and the real slug/namespace.
src/ListFieldTypeServiceProvider.php Registration; anything bound here is an extension point.
src/ListFieldType.php The actual field type. Look for $inputView, getSlug(), getConfig(), and any getValue()/setValue() overrides — those tell you how the array is persisted.
resources/views/input.blade.php The markup that produces name="field[]". Publish it before you customize anything.

3. First use case — a Page "Features" list. In the control panel: Content → Pages → Fields → New Field → choose List. Set the type to text, then on the page form you get a repeating row of inputs. In your page view:

{% if entry.features is not empty %}
    <ul class="features">
        {% for feature in entry.features %}
            <li>{{ feature }}</li>
        {% endfor %}
    </ul>
{% endif %}

Before you trust that, dump the value once ({{ entry.features|json_encode }}) to confirm whether the field type decodes for you or hands back a raw JSON/serialized string.


Implementation Patterns

Declaring the field in a migration (preferred for repeatable deploys)

Never rely on clicking through the CP for production schema. Configure it in code:

use Anomaly\Streams\Platform\Database\Migration\Migration;

class AddFeaturesToPages extends Migration
{
    protected $stream = ['slug' => 'pages'];

    public function up()
    {
        if (!$field = $this->fields()->findBySlugAndNamespace('features', 'pages')) {
            $field = $this->fields()->create([
                'namespace' => 'pages',
                'slug'      => 'features',
                'type'      => 'emange.list_field_type', // verify against addon:list
                'name'      => 'Features',
                'config'    => [
                    'mode'        => 'text',   // or 'textarea'
                    'placeholder' => 'Add a feature…',
                    'rows'        => 1,
                ],
            ]);
        }

        if (!$this->assignments()->findByFieldIdAndStreamId($field->getId(), $this->stream->getId())) {
            $this->assignments()->create([
                'stream_id' => $this->stream->getId(),
                'field_id'  => $field->getId(),
            ]);
        }
    }
}

The string in type is addon-namespace.field_type-slug. If emange.list_field_type fails validation, run php artisan tinker and inspect the fields table or the addon listing to get the exact slug the class exposes.

Normalizing on read with a presenter

Don't trust the raw value to be an array everywhere (API responses, older rows, exports). Add one accessor and use it consistently:

class PagePresenter extends Presenter
{
    public function features()
    {
        $value = $this->object->features;

        if (is_string($value)) {
            $value = json_decode($value, true) ?: [];
        }

        return collect((array) $value)
            ->map(fn ($row) => is_array($row) ? ($row['value'] ?? '') : $row)
            ->map(fn ($row) => trim((string) $row))
            ->filter()
            ->values();
    }
}

Pairing with a searchable companion field

Streams search and WHERE clauses will not look inside the array. If editors must find entries by keyword, keep a plain text field in sync via an observer or the repository's save hook, or index externally:

$page->search_blob = $page->features()->implode(' ');

Multi-language

Streams stores the field value per locale. That means a translation can have a different number of rows than the default — which is usually what you want, but it also means the CP translation tab must be re-saved for new rows to appear. Check your templates don't assume count($en) === count($fr).

Custom admin markup (drag-to-reorder)

Publish the view, then:

php artisan addon:publish emange.list_field_type

Rewrite resources/views/input.blade.php keeping the name="{{ $fieldType->getFormName() }}[]" convention, and wire Sortable.js to the container. The posted order is the stored order — there is no separate sort_order column.

Validation

Field types don't validate arrays deeply out of the box. Put rules in the form request or the field's config:

'features'   => 'array|max:20',
'features.*' => 'nullable|string|max:255',

Then trim and de-duplicate in a saving observer so the stored array never contains blanks.


Gotchas and Tips

  • Bitbucket-only distribution. There is no Packagist entry, so a plain composer require fails. Add the repositories VCS block and pin a tag or commit hash — with zero stars and zero dependents there is no upstream churn signal, so dev-master is a liability.
  • "The field type isn't in the dropdown" almost always means one of: addon not installed (php artisan addon:install emange.list_field_type), autoloader stale (composer dump-autoload), or the addon class not being discovered because "type" in composer.json isn't streams-addon. Reinstall with php artisan addon:reinstall emange.list_field_type after touching the service provider.
  • Column size bites silently. The value is a serialized/JSON array in a single column. A VARCHAR(255) will truncate mid-JSON and give you null on decode with no error. Use TEXT on creation, and MEDIUMTEXT/JSON if editors paste long blocks. On MySQL non-strict mode this truncates without warning — check SHOW WARNINGS if data "disappears".
  • foreach over null. {% for x in entry.field %} on an empty or null value can throw depending on the Twig version and how the field type returns empties. Always guard with is not empty or ?? [].
  • Blank trailing rows. Every submission from the UI tends to include one or more empty strings. Filter them in the presenter and before save; otherwise your implode(', ') output gets stray commas and your count() is wrong.
  • The [] suffix is load-bearing. If you customize the view, a missing [] on the name attribute means only the last row posts. Same for hidden "row count" inputs — keep them in sync with the DOM or you'll drop rows on save.
  • Switching an existing text field to this type corrupts nothing but breaks reading: old rows hold a bare string, new rows hold an array. Write a one-off migration that wraps scalars: $value = is_array($value) ? $value : [$value];.
  • Config precedence. Options are read through $this->getConfig('mode'), not $this->mode. Assignment-level config overrides field-level config, which is genuinely useful for having the same field behave as text on one stream and textarea on another — but it's a common source of "why is my rows setting ignored".
  • Editing the published view requires php artisan view:clear. Blade compiles to storage/framework/views; without clearing you'll swear your change wasn't saved.
  • Not searchable, not sortable, not queryable. If a stakeholder asks "can we filter entries by feature?", the answer is no — go back to a real stream + relationship at that point. This addon is for display-oriented lists only.
  • Extension points worth knowing: publish the input view for markup/JS, override getValue() in a subclass (or bind your own in the service provider) to force collect(), and use a Streams observer on the entry model for normalization and companion-field sync. Avoid overriding setValue() unless you've read the parent — the platform's locale/save pipeline is easy to break there.
  • Vendor risk. Zero stars, zero dependents, Bitbucket-hosted, and no visible release cadence. Fork it into your own VCS, pin the commit, and keep the input.blade.php published so a future upstream change (or disappearance) can't take your admin UI with it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings