zynfly / laravel-meta
Easily add and manage meta data for your Laravel models with a clean, intuitive primary-meta table approach.
Fund package maintenance!
zynfly
Installs: 5
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/zynfly/laravel-meta
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^9.0||^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
This package is auto-updated.
Last update: 2026-02-11 14:25:58 UTC
README
Easily add and manage meta data for your Laravel models with a clean, intuitive primary-meta table approach — inspired by WordPress's wp_postmeta / wp_usermeta pattern.
Why Laravel Meta?
Sometimes you need to store flexible, schema-less attributes on your Eloquent models without constantly running migrations. Laravel Meta gives each model its own {table}_meta table with key / value rows — just like WordPress does for posts, users, and comments.
posts posts_meta
┌────┬─────────┐ ┌────┬─────────┬──────────┬────────┐
│ id │ title │ │ id │ post_id │ key │ value │
├────┼─────────┤ ├────┼─────────┼──────────┼────────┤
│ 1 │ Hello │───────▶ │ 1 │ 1 │ subtitle │ World │
│ │ │ │ 2 │ 1 │ color │ blue │
└────┴─────────┘ └────┴─────────┴──────────┴────────┘
Installation
composer require zynfly/laravel-meta
Publish the config file (optional):
php artisan vendor:publish --tag="meta-config"
Quick Start
1. Generate a meta migration
php artisan make:meta-migration posts
# Creates: database/migrations/xxxx_xx_xx_xxxxxx_create_posts_meta_table.php
You can also specify a custom foreign key:
php artisan make:meta-migration posts --foreign-key=article_id
Then run the migration:
php artisan migrate
2. Add the trait to your model
use Zynfly\LaravelMeta\Traits\HasMetaTable; class Post extends Model { use HasMetaTable; protected $fillable = ['title', 'content']; }
3. Use it
// Create with meta — attributes not in the table are stored as meta automatically $post = Post::create([ 'title' => 'Hello World', // stored in posts table 'subtitle' => 'A great post', // stored in posts_meta table 'color' => 'blue', // stored in posts_meta table ]); // Read meta transparently via attribute access echo $post->subtitle; // "A great post" // Update meta via attribute assignment $post->subtitle = 'An awesome post'; $post->save();
API Reference
Transparent attribute access
Meta values are accessible as regular model attributes. The trait automatically separates table columns from meta on create, update, and read:
$post->subtitle; // reads from meta cache (no N+1) $post->subtitle = 'New'; // marks as dirty meta $post->save(); // persists to meta table
Explicit meta methods
For more control, use the explicit API:
// Get $post->getMeta('color'); // "blue" $post->getMeta('missing', 'default'); // "default" $post->getAllMeta(); // ['subtitle' => '...', 'color' => 'blue'] $post->hasMeta('color'); // true // Set (persists immediately) $post->setMeta('color', 'red'); $post->setMeta([ // bulk set 'color' => 'red', 'size' => 'large', ]); // Remove $post->removeMeta('color'); $post->removeMeta(['color', 'size']); // bulk remove // Refresh cache from database $post->refreshMetaCache();
All setMeta / removeMeta calls are chainable:
$post->setMeta('a', '1')->setMeta('b', '2')->removeMeta('c');
Meta relationship
Access raw meta records via the meta() HasMany relationship:
$post->meta; // Collection of Meta models $post->meta()->where('key', 'color')->first()->value; // "blue"
Type casting
The Meta model provides a getTypedValue() helper:
$meta = $post->meta()->where('key', 'count')->first(); $meta->getTypedValue('integer'); // 42 $meta->getTypedValue('boolean'); // true $meta->getTypedValue('json'); // ['foo' => 'bar']
Supported types: string, int/integer, float/double, bool/boolean, array/json.
Programmatic table creation
You can also create/drop meta tables in your own migrations using the facade:
use Zynfly\LaravelMeta\Facades\LaravelMeta; // In a migration public function up(): void { LaravelMeta::createMetaTableFor('posts'); // posts_meta with post_id FK LaravelMeta::createMetaTableFor('posts', 'article_id'); // custom FK name } public function down(): void { LaravelMeta::dropMetaTableFor('posts'); }
The generated meta table includes:
| Column | Type | Notes |
|---|---|---|
id |
bigint |
Primary key |
{singular}_id |
unsigned bigint |
Indexed foreign key |
key |
varchar(255) |
Meta key |
value |
text |
Meta value (nullable) |
created_at |
timestamp |
|
updated_at |
timestamp |
Plus a composite index on ({foreign_key}, key) for query performance.
Configuration
Published config file (config/meta.php):
return [ // Column type for 'value': "text" or "longText" 'value_column_type' => 'text', // Wrap meta insert/update in DB::transaction() 'use_transactions' => true, ];
How It Works
- Boot — The trait caches table column names via
Schema::getColumnListing(). - Create —
getAttributesForInsert()splits attributes into table columns vs. meta. After the model is inserted, meta rows are batch-inserted viacreateMany(). - Update —
getDirtyForUpdate()splits dirty attributes.performUpdate()ensures meta is persisted even when only meta attributes changed. - Read —
getAttribute()first checks the parent model, then falls back to the meta cache. All meta is loaded in a single query on first access (no N+1). - Delete — All related meta records are deleted when the model is deleted.
All write operations are wrapped in DB::transaction() for data integrity.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.