---
title: "The HasAttributes trait — Attributes for Laravel | Roundly"
description: "Add the HasAttributes trait and contract to any Eloquent model to give it typed, queryable attributes — plus the method reference."
url: https://roundly-consulting.com/open-source/docs/attributes-for-laravel/has-attributes
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Attributes for Laravel](https://roundly-consulting.com/open-source/docs/attributes-for-laravel.md)

# The HasAttributes trait

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

```php
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:

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

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

A row created straight through the relation — $product->attachedAttributes()->create(\[...\]) — still stores its value under the name’s definition: in its type, encrypted when the definition says so and with its unique hash. It bypasses Attributes::for(), though — no strict mode, rules, history or events — so write through the facade or the model methods.

## Reading methods

| Method | Returns | Purpose |
| --- | --- | --- |
| `attachedAttributes()` | MorphMany | The relation to the stored Attribute rows. |
| `getAttachedAttributes()` | Collection | name → typed value for every stored attribute. |
| `getAttachedAttribute($name)` | ?Attribute | One Attribute model, or null. |
| `getAttachedAttributeValue($name)` | mixed | The typed value, or the definition’s default. |
| `getAttachedAttributeValueAsString($name)` | ?string | The value in its storage-string form. |
| `hasAttachedAttribute($name)` | bool | Whether the attribute is present. |
| `getAttachedAttributeMeta($name)` | ?Collection | The attribute’s metadata. |
| `attr($name)` | StoredAttributeValue | Typed accessor with defaults applied. |
| `attributeInt / Float / Bool / Array / Date($name)` | ?int … ?Carbon | Per-type readers. |
| `history($name = null)` | Collection | Recorded revisions, newest first. |
| `attributeHistory($name = null)` | MorphMany | The revisions relation. |

## Writing methods

| Method | Same 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/attributes-for-laravel.md)
