---
title: "Tags and links — Tickets for Laravel | Roundly"
description: "Agent-only coloured tags, global or per project, with permanent names — and typed links between tickets with cycle checks."
url: https://roundly-consulting.com/open-source/docs/tickets-for-laravel/tags-links
language: en
---

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

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

# Tags and links

Tags are agent-only labels, global or per project:

```php
Tickets::tags()->create('printer', color: '#f24e29', project: $helpdesk);
Tickets::tags()->findOrCreate('vip');
Tickets::tags()->rename($tag, 'printers');
Tickets::tags()->recolor($tag, null);
Tickets::tags()->delete($tag);                                // returns the trashed tag
Tickets::tags()->find('printer', $helpdesk);                  // by id or slug: the project's, else the global one
Tickets::tags()->all($helpdesk);                              // global + the project's, by name

Tickets::for($ticket)->tags()->attach(['printer', 'vip'])->detach('spam');
Tickets::for($ticket)->tags()->sync(['printer']);
Tickets::for($ticket)->tags()->all();
```

- A name is 1–64 characters (trimmed) and must slugify to something; a colour is #rrggbb (stored lower-case) or null — otherwise InvalidTagException.
- A name is attached by name (created on the fly with tickets.tags.free\_form, else InvalidTagException) or as a TicketTag. A tag’s project must be the ticket’s, or global. A name resolves to a live tag first — the ticket’s project’s, then the global one.
- Deleting a tag takes it off every ticket that carries it: each live ticket records an internal untagged entry and fires TicketUntagged.
- Names are permanent per scope: creating a deleted tag’s name restores the tag row (same id and slug) — empty, so tagging one ticket never silently re-labels others. Renaming onto an existing name is refused; the slug follows renames.
- Attach, detach and sync are idempotent and record one internal activity per call ({added, removed}); TicketTagged / TicketUntagged fire with the names. Tag writes serialise on lock:tags, so concurrent findOrCreate('printer') yields one tag.

## Links

```php
use RoundlyConsulting\Tickets\Enums\LinkType;

Tickets::for($ticket)->by($agent)->links()->add('TCK-9', LinkType::Blocks);
Tickets::for($ticket)->links()->remove($other, LinkType::Blocks);
Tickets::for($ticket)->links()->all();   // list<TicketLinkView>: ticket, type, label, direction
```

| Type | Read from the other end |
| --- | --- |
| `relates_to` | relates\_to (symmetric; stored once, lower key first) |
| `duplicates` | `duplicated_by` |
| `blocks` | `blocked_by` |
| `parent_of` | `child_of` |

Refused with InvalidTicketLinkException: a self link; a blocks / parent\_of link that closes a cycle (a depth-first walk bounded by tickets.links.max\_depth); a second parent; a cross-project link with tickets.links.cross\_project off; removing a link this ticket is not part of. Adding a duplicate link is a no-op. Links to a soft-deleted ticket are hidden and cannot be removed.

remove() takes the link this ticket holds outgoing, else the one it holds incoming. duplicates may run both ways (A duplicates B and B duplicates A, two rows): call remove() from the end whose link should go. The API’s DELETE …/links/{link} removes exactly the row it names.

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