---
title: "Attaching attributes & metadata — Attributes for Laravel | Roundly"
description: "Attach one or many typed attributes, add metadata to any of them, and replace metadata without touching the value."
url: https://roundly-consulting.com/open-source/docs/attributes-for-laravel/attaching-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)

# Attaching attributes & metadata

Attributes::for($model)->set() creates the attribute, or updates it in place when the name already exists — each owner holds one row per name, and a detached name gets its row back with a fresh value and meta:

```php
use RoundlyConsulting\Attributes\Facades\Attributes;

// One attribute — creates it, or updates it in place; returns the Attribute model
Attributes::for($product)->set('color', 'white');
Attributes::for($product)->set('color', 'black'); // same row, new value

// Several at once from a name => value map; attributes you don't name are kept
Attributes::for($product)->setMany([
    'color' => 'white',
    'size'  => 'large',
    'stock' => 12,
]);                                                // Collection<Attribute>
```

## What happens on attach

- Strict mode — with strict on, a name with neither a global nor a model-schema definition throws UnknownAttributeException.
- Validation — a registered name is validated against its type and rules (InvalidAttributeValueException).
- Uniqueness — a unique definition rejects a value another owner already holds (DuplicateAttributeValueException); the unique index settles concurrent writers.
- Owner — the owner must have a database row: a write to a new or permanently deleted model throws AttributesException instead of storing a row with no owner key.
- Persistence — the value is stored in its defined type (else the PHP value’s); encrypted definitions are stored as ciphertext. Without new meta, the stored meta is kept.
- History — with history enabled, an attached or updated revision is recorded.
- Event — AttributeAttached is dispatched once the write’s transaction commits.

setMany() runs the same pipeline for each entry of a name → value map, all or nothing: every value is validated before the first write, and the writes share one transaction.

## On the model

attachAttribute() and attachAttributes() are model shorthand for set() and setMany() — they delegate to the same manager and return the model, so calls chain:

```php
// One attribute — creates it, or updates it in place
$product->attachAttribute('color', 'white');
$product->attachAttribute('color', 'black'); // same row, new value

// Several at once from a name => value map
$product->attachAttributes([
    'color' => 'white',
    'size'  => 'large',
    'stock' => 12,
]);

// Writers return the model, so calls chain
$product->attachAttribute('on_sale', true)->attachAttribute('discount', 0.15);
```

## Metadata

Every attribute can carry a metadata collection — a hex code, a unit, where the value came from. Pass it as an array or a Collection when you set the attribute, or replace it later with meta():

```php
use RoundlyConsulting\Attributes\Facades\Attributes;

// Attach with metadata (an array or a Collection)
Attributes::for($product)->set('color', 'white', meta: ['hex' => '#ffffff']);

// setMany() and sync() take a per-name meta map as their last argument
Attributes::for($product)->setMany(['color' => 'white', 'size' => 'L'], meta: ['color' => ['hex' => '#ffffff']]);

$product->getAttachedAttributeMeta('color'); // Collection: ['hex' => '#ffffff']

// A new value without meta keeps the stored meta
Attributes::for($product)->set('color', 'black');

// Replace the metadata, keep the value
Attributes::for($product)->meta('color', ['hex' => '#f5f5f5', 'source' => 'import']);

// Clear it
Attributes::for($product)->meta('color', null);

// Model shorthand
$product->attachAttribute('color', 'white', collect(['hex' => '#ffffff']));
$product->syncAttributeMeta('color', collect(['hex' => '#f5f5f5']));
```

meta() — and its model shorthand syncAttributeMeta() — replaces the whole metadata collection of the named attribute and leaves its value untouched; if the attribute is not attached yet, it creates the row with a null value. Pass null to clear the metadata. It goes through the same write as a value: strict mode applies, history records a revision and AttributeAttached fires. Since the value is left alone, strict mode is the only check it can fail.

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