NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Attributes for Laravel

The HasAttributes trait

Add the HasAttributes trait and implement the matching contract on any model you want to attach attributes to:

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Attributes\Contracts\HasAttributes as HasAttributesContract;
use RoundlyConsulting\Attributes\Traits\HasAttributes;

class Product extends Model implements HasAttributesContract
{
    use HasAttributes;
}

Any Eloquent model works — products, users, orders, tickets. Attributes link through a polymorphic owner relation, so one table serves every model.

The relation

Stored rows are exposed as the attachedAttributes() morph-many relation. Mind the name: attributes() is the fluent writer, not the relation. Queries on the relation return an AttributeCollection with toKeyValue() and keyByName() helpers:

$product->attachedAttributes;                        // AttributeCollection of Attribute models
$product->attachedAttributes()->where('name', 'color')->first();

Product::query()->with('attachedAttributes')->get();  // eager load for cheap reads

Reading methods

MethodReturnsPurpose
attachedAttributes()MorphManyThe relation to the stored Attribute rows.
getAttachedAttributes()Collectionname → typed value for every stored attribute.
getAttachedAttribute($name)?AttributeOne Attribute model, or null.
getAttachedAttributeValue($name)mixedThe typed value, or the definition’s default.
getAttachedAttributeValueAsString($name)?stringThe value in its storage-string form.
hasAttachedAttribute($name)boolWhether the attribute is present.
getAttachedAttributeMeta($name)?CollectionThe attribute’s metadata.
attr($name)StoredAttributeValueTyped accessor with defaults applied.
attributeInt / Float / Bool / Array / Date($name)?int … ?CarbonPer-type readers.
history($name = null)CollectionRecorded revisions, newest first.
attributeHistory($name = null)MorphManyThe revisions relation.

Writing methods

MethodSame as Attributes::for($model)->…Purpose
attributes()stage()Fluent AttributeWriter for staging several attributes.
attachAttribute($name, $value = null, $meta = null)set()Attach or update one attribute, with optional metadata.
attachAttributes(array $attributes)setMany()Attach or update several from a name → value map.
syncAttributeMeta($name, $meta = null)meta()Replace one attribute’s metadata.
syncAttributes(array $attributes, $forceDelete = false)sync()Make the attributes match the given set exactly.
detachAttribute($name, $forceDelete = false)forget()Remove one attribute.
detachAttributes(array $names, $forceDelete = false)forget()Remove several attributes.
destroyAttributes(array $names, $forceDelete = false)forget()Remove several attributes (same path as detachAttributes).
destroyAttributesExcept(array $names, $forceDelete = false)forgetExcept()Remove every attribute not in the list.
validateAttributes()— (registry validation)Assert required attributes are present and values are valid.

The write methods are shorthand, not a separate path: each one delegates to Attributes::for($this), so host overrides and Attributes::fake() see them. Every writer returns the model, so calls chain; every forceDelete flag defaults to a soft delete. The contract requires attachedAttributes(), getAttachedAttributes(), getAttachedAttributeValue(), hasAttachedAttribute(), attr() and validateAttributes() — the trait provides them all. The query scopes are covered in Query scopes.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.