{"id":33953419,"url":"https://github.com/humweb/taggables","last_synced_at":"2026-05-30T22:31:18.372Z","repository":{"id":295186296,"uuid":"989416457","full_name":"humweb/taggables","owner":"humweb","description":null,"archived":false,"fork":false,"pushed_at":"2025-05-24T19:25:30.000Z","size":54,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-29T00:09:17.835Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/humweb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-24T03:59:05.000Z","updated_at":"2025-09-01T06:24:09.000Z","dependencies_parsed_at":"2025-05-24T05:19:23.263Z","dependency_job_id":"d3d2156a-d313-4c1c-86c8-8c8944c67baa","html_url":"https://github.com/humweb/taggables","commit_stats":null,"previous_names":["humweb/taggables"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/humweb/taggables","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humweb%2Ftaggables","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humweb%2Ftaggables/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humweb%2Ftaggables/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humweb%2Ftaggables/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/humweb","download_url":"https://codeload.github.com/humweb/taggables/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humweb%2Ftaggables/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33712579,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-12T19:59:44.982Z","updated_at":"2026-05-30T22:31:17.819Z","avatar_url":"https://github.com/humweb.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Taggables Package for Laravel\n\n[![tests](https://github.com/humweb/taggables/actions/workflows/run-tests.yml/badge.svg)](https://github.com/humweb/taggables/actions/workflows/run-tests.yml)\n[![codecov](https://codecov.io/gh/humweb/taggables/graph/badge.svg)](https://codecov.io/gh/humweb/taggables)\n\nA powerful and flexible tagging package for Laravel applications with polymorphic relationships support and user-scoped tags.\n\n## Features\n\n- 🏷️ **Polymorphic tagging** - Tag any Eloquent model\n- 👤 **User-scoped tags** - Personal tags for each user alongside global tags\n- 📁 **Tag types** - Organize tags into categories\n- 🔍 **Advanced queries** - Filter models by tags with ease\n- 🚀 **Performance optimized** - Eager loading and query optimization\n- 📊 **Tag statistics** - Popular tags, tag clouds, and more\n- 🎯 **Type hinting** - Full IDE support with proper return types\n- ✨ **Laravel conventions** - Follows Laravel best practices\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require humweb/taggables\n```\n\nPublish the migrations:\n\n```bash\nphp artisan vendor:publish --tag=\"taggable-migrations\"\n```\n\nRun the migrations:\n\n```bash\nphp artisan migrate\n```\n\nOptionally, publish the config file:\n\n```bash\nphp artisan vendor:publish --tag=\"taggable-config\"\n```\n\n## Usage\n\n### Making a Model Taggable\n\nAdd the `HasTags` trait to any model you want to make taggable:\n\n```php\nuse Humweb\\Taggables\\Traits\\HasTags;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Post extends Model\n{\n    use HasTags;\n}\n```\n\n### Basic Tagging Operations\n\n```php\n$post = Post::find(1);\n\n// Add global tags (available to all users)\n$post-\u003etag('laravel');\n$post-\u003etag(['php', 'javascript']);\n\n// Add user-specific tags\n$post-\u003etagAsUser(['favorite', 'important'], $user);\n\n// Remove tags\n$post-\u003euntag('laravel');\n$post-\u003euntag(['php', 'javascript']);\n$post-\u003euntag(); // Remove all tags\n\n// Remove user-specific tags\n$post-\u003euntagAsUser(['favorite'], $user);\n\n// Replace all tags\n$post-\u003eretag(['vue', 'tailwind']);\n$post-\u003eretagAsUser(['personal', 'work'], $user);\n\n// Sync tags (like Laravel's sync method)\n$post-\u003esyncTags(['laravel', 'php', 'mysql']);\n$post-\u003esyncTagsAsUser(['todo', 'urgent'], $user);\n```\n\n### User-Scoped Tags\n\nThe package supports both global tags (available to all users) and user-specific tags:\n\n```php\n// Create a global tag\n$post-\u003etag('laravel'); // Available to all users\n\n// Create a user-specific tag\n$post-\u003etagAsUser('personal-project', $user);\n\n// Mix global and user tags\n$post-\u003etag(['php', 'laravel']); // Global\n$post-\u003etagAsUser(['favorite', 'todo'], $user); // User-specific\n\n// Get only user tags\n$userTags = $post-\u003euserTags($user-\u003eid);\n\n// Get only global tags\n$globalTags = $post-\u003eglobalTags();\n\n// Get all tags (user + global)\n$allTags = $post-\u003etags; // Returns both by default\n```\n\n### Checking Tags\n\n```php\n// Check if the model has a specific tag (checks user + global by default)\nif ($post-\u003ehasTag('laravel', $user-\u003eid)) {\n    // ...\n}\n\n// Check for global tag only\nif ($post-\u003ehasGlobalTag('laravel')) {\n    // ...\n}\n\n// Check for user tag only\nif ($post-\u003ehasUserTag('favorite', $user)) {\n    // ...\n}\n\n// Check if the model has any of the given tags\nif ($post-\u003ehasAnyTag(['laravel', 'php'], $user-\u003eid)) {\n    // ...\n}\n\n// Check if the model has all of the given tags\nif ($post-\u003ehasAllTags(['laravel', 'php'], $user-\u003eid)) {\n    // ...\n}\n```\n\n### Querying by Tags\n\n```php\n// Get all posts with any of the given tags (includes user + global tags)\n$posts = Post::withAnyTags(['laravel', 'php'], null, $userId)-\u003eget();\n\n// Get posts with user-specific tags only\n$posts = Post::withUserTags(['favorite', 'todo'], $user)-\u003eget();\n\n// Get posts with global tags only\n$posts = Post::withGlobalTags(['laravel', 'php'])-\u003eget();\n\n// Get all posts with all of the given tags\n$posts = Post::withAllTags(['laravel', 'php'], null, $userId)-\u003eget();\n\n// Get all posts without the given tags\n$posts = Post::withoutTags(['draft', 'archived'], null, $userId)-\u003eget();\n\n// Simple single tag query\n$posts = Post::taggedWith('laravel', $userId)-\u003eget();\n```\n\n### Using Tag Types\n\nTag types allow you to categorize your tags:\n\n```php\n// Tag with type\n$post-\u003etag(['important', 'urgent'], 'priority');\n$post-\u003etagAsUser(['personal', 'work'], $user, 'category');\n\n// Get tags of a specific type\n$priorityTags = $post-\u003etagsWithType('priority');\n$userCategories = $post-\u003etagsWithType('category', $user-\u003eid);\n\n// Query by tags with type\n$posts = Post::withAnyTags(['important', 'urgent'], 'priority', $userId)-\u003eget();\n```\n\n### Working with the Tag Model\n\n```php\nuse Humweb\\Taggables\\Models\\Tag;\n\n// Find or create a tag\n$tag = Tag::findOrCreate('laravel'); // Global tag\n$tag = Tag::findOrCreateForUser('personal', $user); // User tag\n$tag = Tag::findOrCreateGlobal('php'); // Explicitly global\n\n// Find or create with type\n$tag = Tag::findOrCreate('important', 'priority', $user-\u003eid);\n\n// Find or create multiple tags\n$tags = Tag::findOrCreateMany(['laravel', 'php', 'mysql'], null, $user-\u003eid);\n\n// Search tags\n$tags = Tag::containing('lara')-\u003eget();\n\n// Get tags by user\n$userTags = Tag::forUser($user-\u003eid)-\u003eget();\n$globalTags = Tag::global()-\u003eget();\n$mixedTags = Tag::forUserWithGlobal($user-\u003eid)-\u003eget();\n\n// Get tags by type\n$priorityTags = Tag::withType('priority')-\u003eget();\n\n// Get popular tags\n$popularTags = Tag::popularTags(10, $user-\u003eid); // Mixed popular tags\n$userPopular = Tag::popularUserTags(10, $user-\u003eid); // User's popular tags\n$globalPopular = Tag::popularGlobalTags(10); // Global popular tags\n\n// Get tag cloud (tags with usage weight)\n$tagCloud = Tag::tagCloud($user-\u003eid); // Mixed cloud\n$globalCloud = Tag::tagCloud(); // Global only\n\n// Get unused tags\n$unusedTags = Tag::unusedTags($user-\u003eid)-\u003eget();\n\n// Check tag ownership\n$tag = Tag::find(1);\nif ($tag-\u003eisGlobal()) {\n    // This is a global tag\n} elseif ($tag-\u003eisOwnedBy($user)) {\n    // This is the user's tag\n}\n```\n\n### Tag Suggestions\n\n```php\n// Get tag suggestions based on partial input\n$suggestions = Tag::suggestTags('lara', $user-\u003eid); // Returns user + global tags\n$suggestions = Tag::suggestTags('lara'); // Returns only global tags\n\n// Get related tags (tags often used together)\n$tag = Tag::findOrCreate('laravel');\n$relatedTags = $tag-\u003erelatedTags($user-\u003eid);\n```\n\n### Events\n\nThe package fires events during tagging operations:\n\n- `TagAttached` - Fired when a tag is attached to a model\n- `TagDetached` - Fired when a tag is detached from a model\n- `TagsSynced` - Fired when tags are synced\n\n```php\nuse Humweb\\Taggables\\Events\\TagAttached;\n\n// In your EventServiceProvider\nprotected $listen = [\n    TagAttached::class =\u003e [\n        SendTagNotification::class,\n    ],\n];\n```\n\n### Artisan Commands\n\nClean up unused tags:\n\n```bash\nphp artisan tags:cleanup\n\n# Clean up only user tags\nphp artisan tags:cleanup --user=123\n\n# Clean up only global tags\nphp artisan tags:cleanup --global\n```\n\n## Advanced Usage\n\n### Custom Tag Model\n\nYou can extend the Tag model for additional functionality:\n\n```php\nnamespace App\\Models;\n\nuse Humweb\\Taggables\\Models\\Tag as BaseTag;\n\nclass Tag extends BaseTag\n{\n    // Add your custom methods\n    public function isPublic(): bool\n    {\n        return $this-\u003eisGlobal() || $this-\u003emetadata['public'] ?? false;\n    }\n}\n```\n\nUpdate the config to use your custom model:\n\n```php\n// config/taggable.php\nreturn [\n    'tag_model' =\u003e \\App\\Models\\Tag::class,\n];\n```\n\n### Eager Loading\n\n```php\n// Eager load all tags\n$posts = Post::with('tags')-\u003eget();\n\n// Eager load only user tags\n$posts = Post::with(['tags' =\u003e function ($query) use ($userId) {\n    $query-\u003ewhere('user_id', $userId);\n}])-\u003eget();\n\n// Eager load only global tags\n$posts = Post::with(['tags' =\u003e function ($query) {\n    $query-\u003ewhereNull('user_id');\n}])-\u003eget();\n\n// Eager load tags with specific type\n$posts = Post::with(['tags' =\u003e function ($query) {\n    $query-\u003ewhere('type', 'technology');\n}])-\u003eget();\n```\n\n### Caching\n\nThe package supports caching for better performance:\n\n```php\n// config/taggable.php\nreturn [\n    'cache' =\u003e [\n        'enabled' =\u003e true,\n        'key_prefix' =\u003e 'taggable',\n        'ttl' =\u003e 3600, // 1 hour\n    ],\n];\n```\n\n## Configuration\n\nThe full configuration file:\n\n```php\nreturn [\n    // The tag model to use\n    'tag_model' =\u003e \\Humweb\\Taggables\\Models\\Tag::class,\n\n    // Table names\n    'tables' =\u003e [\n        'tags' =\u003e 'tags',\n        'taggables' =\u003e 'taggables',\n    ],\n\n    // Slug generation\n    'slugger' =\u003e null, // null defaults to Str::slug\n\n    // Tag name validation rules\n    'rules' =\u003e [\n        'name' =\u003e ['required', 'string', 'max:255'],\n    ],\n\n    // Auto-delete unused tags\n    'delete_unused_tags' =\u003e false,\n\n    // User scoping configuration\n    'user_scope' =\u003e [\n        // Enable user-scoped tags\n        'enabled' =\u003e true,\n\n        // Allow creation of global tags (null user_id)\n        'allow_global_tags' =\u003e true,\n\n        // Include global tags when querying user tags\n        'mix_user_and_global' =\u003e true,\n    ],\n\n    // Cache configuration\n    'cache' =\u003e [\n        'enabled' =\u003e true,\n        'key_prefix' =\u003e 'taggable',\n        'ttl' =\u003e 3600,\n    ],\n];\n```\n\n## Migration Notes\n\nThe tags table includes a `user_id` column for user-scoped tags:\n\n```php\nSchema::create('tags', function (Blueprint $table) {\n    $table-\u003eid();\n    $table-\u003estring('name');\n    $table-\u003estring('slug');\n    $table-\u003eforeignId('user_id')-\u003enullable()-\u003econstrained()-\u003ecascadeOnDelete();\n    $table-\u003estring('type')-\u003enullable()-\u003eindex();\n    $table-\u003einteger('order_column')-\u003edefault(0);\n    $table-\u003ejson('metadata')-\u003enullable();\n    $table-\u003etimestamps();\n\n    // Same slug can exist for different users/types\n    $table-\u003eunique(['slug', 'user_id', 'type']);\n    $table-\u003eindex(['user_id', 'type', 'order_column']);\n});\n```\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [Ryan Shofner](https://github.com/ryun)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumweb%2Ftaggables","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhumweb%2Ftaggables","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumweb%2Ftaggables/lists"}