Looking to hire Laravel developers? Try LaraJobs

laravel-user-tags maintained by mujahidabbas

Description
User-scoped tagging for Laravel Eloquent models
Author
Last update
2026/01/28 20:25 (dev-main)
License
Downloads
1

Comments
comments powered by Disqus

Laravel User Tags

Latest Version on Packagist GitHub Tests Action Status Total Downloads License

User-scoped tagging for Laravel Eloquent models. Each user has their own private set of tags that are invisible to and independent from other users' tags.

// User A tags an article
$article->attachUserTag('important'); // Creates User A's "important" tag

// User B tags the same article
Auth::login($userB);
$article->attachUserTag('important'); // Creates User B's separate "important" tag

// Query User A's tagged articles (only sees their own tags)
Auth::login($userA);
$articles = Article::withAnyUserTags(['important'])->get();

Why This Package?

If you've used spatie/laravel-tags and wondered how to give each user their own private tag namespace, this package is for you.

The problem: Global tagging packages share tags across all users. User A's "important" tag is the same as User B's "important" tag.

The solution: This package isolates tags per user. Each user has their own private set of tags that are completely invisible to and independent from other users' tags.

Use cases:

  • Personal CRM systems where users organize their own contacts
  • Bookmark managers with private collections
  • Note-taking apps with personal organization
  • Any multi-user app where users need private categorization

Requirements

  • PHP 8.1, 8.2, or 8.3
  • Laravel 10.x, 11.x, or 12.x

Installation

You can install the package via composer:

composer require mujahidabbas/laravel-user-tags

Publish and run the migrations:

php artisan vendor:publish --tag="user-tags-migrations"
php artisan migrate

Optionally, you can publish the config file:

php artisan vendor:publish --tag="user-tags-config"

Configuration

The published config file (config/user-tags.php) contains:

return [
    // Your User model class
    'user_model' => 'App\\Models\\User',

    // Table names (customize if needed)
    'tables' => [
        'tags' => 'user_tags',
        'taggables' => 'user_taggables',
    ],
];

Usage

Preparing Your Model

Add the HasUserTags trait to any Eloquent model you want to tag:

use Illuminate\Database\Eloquent\Model;
use MujahidAbbas\LaravelUserTags\Traits\HasUserTags;

class Article extends Model
{
    use HasUserTags;
}

Attaching Tags

Use attachUserTag() to add tags to a model. Tags are automatically created if they don't exist:

// Attach a single tag (uses authenticated user)
$article->attachUserTag('important');

// Attach multiple tags at once
$article->attachUserTag(['important', 'read-later', 'favorites']);

// Attach with a specific user (useful in queues, commands, etc.)
$article->attachUserTag('important', $user);

Tags are user-scoped, so User A's "important" tag is completely separate from User B's "important" tag.

Detaching Tags

Use detachUserTag() to remove tags from a model. Non-existent tags are silently ignored:

// Detach a single tag
$article->detachUserTag('important');

// Detach multiple tags
$article->detachUserTag(['important', 'read-later']);

// Detach with a specific user
$article->detachUserTag('important', $user);

Note: This only removes the association; the tag itself is not deleted.

Syncing Tags

Use syncUserTags() to replace all of a user's tags on a model with a new set:

// Replace all current user's tags with exactly these
$article->syncUserTags(['priority', 'work']);

// Sync with a specific user
$article->syncUserTags(['priority', 'work'], $user);

Important: syncUserTags() only affects the specified user's tags. Other users' tags on the same model remain untouched.

Accessing Tags

The userTags() relationship returns all tags attached to a model (from all users):

// Get all tags on this model
$tags = $article->userTags;

// Eager load tags
$articles = Article::with('userTags')->get();

// Filter to a specific user's tags
$myTags = $article->userTags->where('user_id', auth()->id());

Querying by Tags

Query models that have specific tags using scope methods:

// Get posts with ANY of these tags (OR logic)
$posts = Post::withAnyUserTags(['important', 'urgent'])->get();

// Get posts with ALL of these tags (AND logic)
$posts = Post::withAllUserTags(['priority', 'review'])->get();

// Get posts WITHOUT specific tags
$posts = Post::withoutUserTags(['archived', 'spam'])->get();

// Chain scopes
$posts = Post::withAnyUserTags(['tech', 'science'])
    ->withoutUserTags(['archived'])
    ->get();

// With a specific user (useful in queues, commands)
$posts = Post::withAnyUserTags(['important'], $user)->get();

Note: Query scopes respect user isolation - User A's scope queries only consider User A's tags.

Using a Custom User

All tag methods accept an optional user parameter. This is useful when the authenticated user is not available:

// In a queue job
public function handle()
{
    $user = User::find($this->userId);
    $article->attachUserTag('processed', $user);
}

// In a console command
$user = User::find($userId);
$article->syncUserTags(['archived'], $user);

If no user is authenticated and none is passed, a NoAuthenticatedUserException is thrown:

use MujahidAbbas\LaravelUserTags\Exceptions\NoAuthenticatedUserException;

try {
    $article->attachUserTag('important');
} catch (NoAuthenticatedUserException $e) {
    // Handle missing user
}

Important: MorphMap Configuration

This package uses polymorphic relationships to attach tags to any model. By default, Laravel stores the full class name (e.g., App\Models\Article) in the database.

Why MorphMap matters:

  • If you rename or move a model class, existing tag associations break
  • Full class names are verbose and expose your application structure
  • Consistent short names make database queries cleaner

Recommended: Configure MorphMap in your AppServiceProvider:

use Illuminate\Database\Eloquent\Relations\Relation;

public function boot(): void
{
    Relation::enforceMorphMap([
        'article' => \App\Models\Article::class,
        'post' => \App\Models\Post::class,
        'comment' => \App\Models\Comment::class,
        // Add all models that use HasUserTags
    ]);
}

This ensures stable, clean identifiers in your user_taggables table instead of full class paths.

Alternatives

If you need global tags (shared across all users), use spatie/laravel-tags instead. It's excellent for content categorization, blog post tags, and similar use cases where tags are public.

This package is designed to complement spatie/laravel-tags, not replace it. You can use both in the same application:

  • spatie/laravel-tags for public/global tags (e.g., blog categories)
  • mujahidabbas/laravel-user-tags for private/user-scoped tags (e.g., personal bookmarks)

Testing

composer test

Static Analysis

composer analyse

Code Style

composer format

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.

Roadmap

Future releases may include:

  • Tag management utilities (Tag::forUser(), tag search/autocomplete)
  • Events (UserTagAttached, UserTagDetached, UserTagCreated)
  • Tag metadata (colors, icons via JSON column)
  • Tag usage statistics

See the GitHub issues for feature requests and discussions.

Credits

License

The MIT License (MIT). Please see License File for more information.