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

Properties Laravel Package

mibo/properties

Mibo Properties is a Laravel package for adding flexible property data to your app. Define, store, and query custom properties with reusable models and helpers, making it easy to power searchable listings, filters, and detail views without duplicating schema logic.

View on GitHub
Deep Wiki
Context7

Getting Started

mibo/properties is a small, single-maintainer Laravel package (MIT, zero dependents, no stars) for attaching arbitrary named properties to Eloquent models — the kind of thing you reach for when a table would otherwise grow a dozen nullable, mostly-unused columns, or when tenants/users need to define their own fields at runtime.

Because the package is unpublished in terms of community adoption, treat the README and src/ as the source of truth rather than blog posts. The install path is the standard Laravel one:

composer require mibo/properties
php artisan vendor:publish --provider="Mibo\Properties\PropertiesServiceProvider"
php artisan migrate

Publishing typically yields a config file (table names, morph map, default cast behaviour) and one or two migrations (a definitions/properties table and a polymorphic pivot or value table). Run php artisan migrate before touching any model, then read the config file end-to-end — for packages this small the config is the documentation for behaviour switches.

Apply the package trait to the model that should own the properties:

use Mibo\Properties\Concerns\HasProperties;

class Product extends Model
{
    use HasProperties;
}

Where to look first, in order: the config file, the trait (Concerns/HasProperties.php or equivalent), the migration (to understand indexing and column types), then the cast/value classes. The trait tells you the real public API; the migration tells you what queries will and won't be fast.

First use case: let an admin add a warranty_months field to products without a deploy. Store a definition (warranty_months, type integer), attach a value per product, render it in the edit form from the definitions list, and expose it in an API resource. That end-to-end loop — define, set, read, render — is the whole package in one flow.

Implementation Patterns

Definition-then-value. EAV-style packages almost always separate schema (property definitions, often keyed by a key/slug and a type) from data (the per-model value). Model this explicitly in your app: a PropertyDefinition admin CRUD plus a HasProperties model. Resist letting users create definitions implicitly on first write — you lose validation and get typo-created keys.

Scoping definitions. If only some models can have a property, scope definitions by model class or a "property set"/group. This is the difference between a usable admin UI ("these 6 fields apply to products") and an unusable one (200 fields, all optional, all rendered).

Write path. Wrap multi-property writes in a transaction and prefer a single syncProperties([...])-style call over N individual setters if the package offers one — the difference is N queries vs. one upsert. Validate at the boundary with Rule::in($definitions->pluck('key')) plus per-type rules; the package will happily store a string in an integer property if you let it.

Read path. Read through the accessor ($product->property('warranty_months'), or $product->warranty_months if magic accessors are wired) rather than querying the values table directly. Eager-load properties on index pages — a list of 50 products with 8 properties each is 400+ queries without with('properties').

Casting at the edge. Do the type conversion once, in an API resource or view model, not in Blade. A JSON resource that emits warranty_months as an int and is_fragile as a bool keeps JavaScript from drowning in "0" and "false".

Form generation. Generate the admin form from definitions: key => type maps to a component (text, number, checkbox, select with allowed values, textarea). This is where the package earns its keep — a new field becomes a row in a table, not a migration plus a Blade edit plus a validation rule.

Integration points. Filament/Nova field generation from definitions, CSV import mapping unknown columns to definitions, and a PropertyChanged event (or model observer) if downstream systems need to know schema changed. Keep a properties() relationship available so existing with()/whereHas() habits carry over.

Gotchas and Tips

  • Zero dependents is a real risk. No one else has hit the edge cases yet. Pin the version in composer.json, and wrap the package behind your own thin repository/service (e.g. PropertyRepository) so a future swap is one class, not 40 call sites.
  • N+1 is the default failure mode. Any ->get() over a paginated list needs with('properties'). Add it to the model's $with if properties are always rendered, but be aware that inflates every query in the app.
  • Querying values is slow until you index. whereHas('properties', fn($q) => $q->where('key', 'x')->where('value', 'y')) degrades fast as the values table grows. Ensure the migration has a composite index on (key, value) or (model_type, model_id, key). If the published migration lacks it, add your own — this is the single highest-value fix in an EAV package.
  • Type inconsistency. A column typed string forces you to serialise everything. Booleans become "1"/"0", dates become strings with timezone drift, JSON becomes a double-encoded blob. Confirm how the package casts on read and write; a mismatch shows up as true on save and "true" on load.
  • Morph map matters. If the pivot stores model_type, run Relation::enforceMorphMap() before you have production rows, or renaming a class orphans every property silently.
  • Caching. Definitions are read on nearly every request and change rarely — cache them (Cache::rememberForever, forget on save). Values should not be cached unless you have invalidation wired; stale EAV data is worse than a query.
  • Mass assignment and deletion. Properties usually live outside $fillable. Also check what happens to values when the parent model is deleted (FK cascade vs. orphan rows) and when a definition is deleted (soft delete vs. hard delete of values).
  • Extension points to look for: custom cast/value classes (register your own money or enum type), a driver or repository binding in the service provider, and events fired on set/delete. If none exist, the trait is your only seam — subclass it rather than editing vendor code.
  • Debugging tip: log the raw row ($model->properties()->get()->toArray()) before trusting the accessor. Most "the package is broken" reports resolve to a value stored under a slightly different key than the one read, usually from a migration or seeder that used a slug where the form used a label.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect