---
title: "Tenant-scoped tags — Tags for Laravel | Roundly"
description: "Tags owned by a team, client or project next to shared global ones — scoped catalogs, strict write isolation, global-only types and scoped queries."
url: https://roundly-consulting.com/open-source/docs/tags-for-laravel/tenant-scopes
language: en
---

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

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

# Tenant-scoped tags

A tag is global, or owned by one scope model — a team, a tenant, a project. A taggable model names its scope through tagScope(); it then sees its scope’s tags plus the global ones, and a free-form name typed on it is created in its scope:

```php
// Free-form tags typed on a task belong to its team (Task::tagScope() returns $this->team)
$task->attachTags('printer', 'label');     // the team's "printer", else the global one, else a new team tag

// Catalog handles: reads see the scope's tags plus the global ones…
$labels = Tags::type('label')->within($team);
$labels->all();
$labels->suggest('pri');

// …or only the scope's
Tags::type('label')->within($team, includeGlobal: false)->all();

// Writes match the handle's scope exactly
$labels->make('Urgent')->create();                     // a team tag
Tags::type('label')->make('Urgent')->create();         // a global tag, for every team
$labels->delete($globalTag);                           // TagScopeMismatchException — never touches a global tag

// A tag of another scope is refused on a model, exactly like a missing one
$task->attachTags($tagOfAnotherTeam, 'label');         // TagScopeMismatchException (code tag_not_found)
```

- A string resolves in the model’s scope first, then globally. The same name may exist as a team tag and as a global tag; the team’s wins for that team’s models.
- Uniqueness is per type and scope, so two teams may each own a printer label.
- Writes through a catalog handle match its scope exactly: a within($team) handle edits, moves and deletes the team’s own tags and never a global tag every tenant sees; an unscoped handle writes global tags only. A tag of another scope throws TagScopeMismatchException.
- A tag or key of another scope answers exactly like a missing one — in every verb, in TagsRule and in the API — so no response tells someone else’s tag from no tag.
- A scope model must be saved and keyed by tags.key\_type. Its internal scope key is its morph class and key, at most 300 characters — register a short morph alias for a long class name.

## Global-only types

Some kinds of tags should be one list for everyone — a country, a main category:

```php
Tags::define('country')->globalOnly();       // one shared list for every tenant

$venue->attachTags('Austria', 'country');          // looked up and created globally, tagScope() ignored
Tags::type('country')->within($team)->make('Austria')->create();   // InvalidTagException: always global
```

## Scoped queries

```php
// Strings resolve in every scope of the type, unless within: narrows them to a scope plus global
Task::withAnyTags(['printer'], 'label', within: $team)->get();

Tag::query()->ofType('label')->within($team)->get();          // the team's tags and the global ones
Tag::query()->ofType('label')->within($team, false)->get();   // the team's only
Tag::query()->ofType('label')->global()->get();               // global only
```

In the query scopes, strings match the named tag in every scope of the type — in each scope at its own first matching locale of the chain — unless within: narrows them, because your own query already narrows the models. Tag models and keys match exactly the tags they are.

## When a model’s scope changes

Taggings keep the tags they point at. A model whose tagScope() changes keeps tags of its old scope until the next sync of that type drops them; tags:doctor counts such taggings as a warning.

## 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/tags-for-laravel.md)
