---
title: "Categories and priorities — Tickets for Laravel | Roundly"
description: "Nested, translatable categories with a default priority, assignee and due offset — plus the weighted priority enum and how to swap it."
url: https://roundly-consulting.com/open-source/docs/tickets-for-laravel/categories-priorities
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)

# Categories and priorities

A tree of translatable categories, global or per project, each with defaults for new tickets. The builder and create(CategoryData) run the same action:

```php
use RoundlyConsulting\Tickets\DataTransferObjects\UpdateCategoryData;

$hardware = Tickets::categories()->make(['en' => 'Hardware', 'sk' => 'Hardvér'])  // a string = the current locale
    ->description(['en' => 'Laptops, printers, peripherals'])
    ->project($helpdesk)            // or none: a global category
    ->under($parent)                // or none: a root
    ->position(10)
    ->defaults(priority: 'high', assignee: $itTeam, dueIn: 'P1D')   // or dueIn: CarbonInterval::day()
    ->create();

Tickets::categories()->update($hardware, new UpdateCategoryData(position: 20, defaultDueIn: null));
Tickets::categories()->move($hardware, $newParent, position: 0);      // null parent = to the root
Tickets::categories()->delete($hardware, reassignTo: $general);       // soft delete
Tickets::categories()->restore($hardware);
Tickets::categories()->tree($helpdesk);   // roots + children: the global tree plus the project's own
```

## Rules

Each refusal throws InvalidCategoryException:

- No move under itself or a descendant (cycle); depth is capped by tickets.categories.max\_depth.
- A child’s project equals its parent’s; a global parent may have project children. A ticket’s category is global or of the ticket’s project.
- An inactive category is refused for new tickets; existing tickets keep it.
- Deleting a category that has live tickets needs reassignTo — an active category, not itself, global or of the same project. Each ticket moves exactly as categorize() moves it (a category\_changed entry, TicketCategoryChanged, the target’s required fields enforced — a ticket missing one stops the whole delete). A category with subcategories cannot be deleted; a restore brings it back as it was.
- Names must not be empty; default\_priority must be a value of the priority enum; default\_due\_in a positive ISO-8601 duration — given as one ('PT4H') or as a CarbonInterval, stored as the duration either way; default\_assignee an Assignee (an ineligible default assignee leaves the ticket unassigned).

Slugs come from the name in the app’s fallback locale, unique among siblings in a project, and are made once — a rename keeps the slug. Category writes serialise on the lock:categories advisory row, which keeps the cycle check and slug uniqueness race-free.

## Defaults

Defaults apply once, at creation, only for what the caller did not give: the caller → the nearest ancestor category that sets one → config. Changing a ticket’s category later re-applies nothing.

## Priorities

Enums\\TicketPriority — low (weight 10), normal (20), high (30), urgent (40) — is stored as a string; tickets.priority.default (normal) applies when neither the caller nor the category sets one:

```php
Tickets::for($ticket)->prioritize('urgent');                   // TicketPriorityChanged
Ticket::query()->withPriority('high', 'urgent')->orderByPriority()->get();
$ticket->priority();                                            // TicketPriority::Urgent, or null
```

A stored value the enum no longer has reads as null (never a 500), sorts last, presents color: null in the API, and tickets:doctor counts it.

## Your own priority scale

Point tickets.priority.enum at any string-backed enum implementing Contracts\\Priority (weight(): int) and set tickets.priority.default to one of its values. orderByPriority() and the API’s priority sort follow the weights; tickets:doctor checks that the enum and the default are usable.

## 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)
