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

Laravel Property Bag Laravel Package

zachleigh/laravel-property-bag

Add a flexible property bag to Laravel Eloquent models: store arbitrary key-value data as JSON without extra tables or migrations, with a clean API for setting, getting, and managing custom properties.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What it is. zachleigh/laravel-property-bag attaches an arbitrary key/value "property bag" to any Eloquent model via a trait and a single polymorphic side table. It replaces per-entity schema churn (a settings column or a dozen nullable columns) with runtime get/set-style helpers. The core idea is sound and well understood; the artifact is not.

Architecture fit. Good fit for sparse, heterogeneous, read-mostly per-entity configuration — per-tenant feature toggles, per-user notification preferences, per-integration endpoints. Explicitly poor fit for anything you must WHERE, ORDER BY, JOIN, or aggregate on: a key/value side table turns every such query into a join plus string comparison, and defeats indexes. Architecturally the package sits in an awkward middle layer between deploy-scoped config (config/) and first-class domain columns, and that middle layer is exactly where unowned, untyped data accumulates. The realistic failure mode of this pattern is not technical, it is governance: a settings bag with no key registry becomes a shadow schema within two quarters. Any adoption decision must pair the package with a documented key registry and owner per key.

Integration feasibility. Low as-is. The repository is archived with a final release in January 2020, which places it in the Laravel 5.5–6.x era. Its Composer constraints on illuminate/* will almost certainly refuse installation on Laravel 9/10/11, and it has not been exercised against PHP 8.x. Therefore composer require is not an integration path; fork-and-vendor or reimplement is the integration path. Mitigating factor: the surface area is small (a trait, a couple of value/collection classes, one migration), so the cost of taking ownership is measured in days, not weeks. The real cost is perpetual ownership of a dependency that will never receive another upstream commit.

Technical risk.

Risk Likelihood Impact Mitigation
Abandoned upstream; no security or framework-compat patches Certain Medium Fork under org namespace, pin, add to dependency review queue
Composer/API breakage on Laravel 10/11 upgrade High Medium Vendor the code; treat as first-party
N+1 queries (settings lazy-loaded per model in collections) High Medium–High Eager-load the relationship by default on consuming models; add request-scoped cache
Unbounded table growth / shadow schema Medium Medium Key registry, TTL/ownership metadata, periodic audit query
Type coercion drift (bool/int/null vs string storage) Medium Medium Enforce cast metadata per key; contract tests
Settings leaking into API responses via toArray()/$appends Medium High Explicitly hide the relationship; exclude from serialization
Orphan rows on model delete Medium Low FK/cascade or scheduled cleanup
Multi-key writes not atomic Medium Medium Wrap in transactions at call sites

Key questions for engineering.

  1. Do we need to query by setting value ("all tenants where beta_x = true"), or only load settings for a known model? Only the latter justifies this pattern.
  2. What is the expected row count per model and in total at 12/24 months — is this 10k rows or 50M?
  3. Which Laravel and PHP versions must this run on today, and on the next upgrade?
  4. Is there an existing JSON column or settings table in the schema we would be duplicating?
  5. Who owns the key namespace, and how is a key deprecated once shipped?
  6. Do settings ever contain secrets? If yes, they need encryption at rest and must never serialize.
  7. Are settings read on hot paths (every request), and is a cache + invalidation strategy budgeted?
  8. Is there a hard requirement for per-model settings that native json column casting plus a typed accessor cannot satisfy?

Integration Approach

Stack fit. The package is idiomatic Laravel — service provider, trait, Eloquent relationship, migration stub — so it will "feel" native to the team. That is a genuine ergonomics plus and a genuine risk: it lowers the barrier to writing settings that should have been columns.

Options considered.

Option Effort Fit Verdict
A. Adopt as-is 0.5d Only on legacy Laravel 6 Reject — locks the app to an EOL framework
B. Fork, bump constraints, vendor 2–3d + ongoing High if polymorphic single table is genuinely needed Viable
C. Reimplement as ~150 LOC trait over a native json column 2–3d High for model-scoped settings; no cross-model queries Recommended default
D. spatie/laravel-schemaless-attributes 1d Actively maintained, same schema-less idea Preferred if a maintained dependency is acceptable
E. Bespoke typed settings table with a key registry 4–5d Highest control Choose if settings are business-critical

Recommendation. Do not adopt this package as a dependency. The opportunity score of 0.27 is consistent with what the code shows: the concept is commoditized (a JSON column, or a maintained alternative, covers it) and the artifact is unmaintained. Use the repository as a reference implementation — read its trait, migration, and type-casting logic, then either reimplement it (Option C) or take the maintained alternative (Option D). Reserve Option B for the narrow case where the team specifically needs the polymorphic side-table shape and wants to preserve the existing API verbatim.

Compatibility. Assume untested on Laravel 7+ and unsupported on PHP 8.1+ until proven otherwise. If forking, the work is: widen illuminate/* and php constraints, run the package's tests on the current matrix, fix dynamic-property and null-to-string deprecations, and verify the migration stub matches current Blueprint syntax. Budget a half-day for the test matrix alone.

Sequencing.

  1. Spike (1d) — inventory every proposed setting; classify each as column, config, or bag.
  2. Schema decision (1d) — JSON column vs side table, driven by the answers to questions 1 and 2.
  3. Implement or fork (2–3d) — trait, migration, cast handling, relationship hiding, contract tests.
  4. Registry + guardrails (1d) — enumerated key constants, an allowed-key validation test, a settings table size alert.
  5. Backfill (1–2d) — if replacing scattered columns, dual-read then dual-write, with a reconciliation query.
  6. Rollout — canary one non-critical model (e.g. notification preferences) before anything billing-adjacent.

Operational Impact

Maintenance. Zero upstream. The MIT license permits a fork, but from day one we absorb framework-compat work, PHP deprecation fixes, and any security issue found in code we did not write. Add it to the dependency register with an explicit "vendored, no upstream" flag and a named owner — an unowned vendored package is the classic path to a surprise during a framework upgrade.

Support. No vendor, no maintainer, and community answers will increasingly reference Laravel 5/6 idioms that no longer apply. The supportable position is: the code is ours, the runbook is ours, and the API surface is frozen to what we use. Keep the vendored code minimal — strip unused casting/collection features rather than carrying them.

Scaling. Row count is models × settings-per-model. At 100k models with 8 settings each, that is 800k rows — fine with a unique index on (model_type, model_id, name), but bad without it. Two scaling hazards dominate: N+1 reads when rendering collections, and write amplification when a single action touches many models. Mitigations: always eager-load, add a request-scoped memoization layer, and if read volume justifies it, a Redis cache keyed by model with invalidation on saved/deleted model events. Note that the caching layer must handle multi-node invalidation correctly — stale settings are a subtle, hard-to-reproduce bug class.

Failure modes. Silent null on a missing key rather than a loud error (prefer explicit defaults at every call site); type drift when a key's stored representation changes and old rows are not migrated; key collisions across teams sharing one bag; orphan rows after model deletion; partial writes on multi-key updates outside a transaction; accidental serialization of settings into API payloads; and the quiet one — settings that were once load-bearing become unread, with nobody able to prove they are safe to delete.

Ramp-up. Technically trivial: a competent Laravel engineer is productive in under half a day. Organizationally expensive: the discipline of when not to use it and the key-registry habit take deliberate enforcement. Budget a short design-review checklist item — "new setting: column, config, or bag?" — or the bag will absorb fields that belong in the schema, and the cost of reversing that later is a data migration, not a code change.

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.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo