---
title: "Extension points — Tickets for Laravel | Roundly"
description: "Every swap point — contracts and their shipped implementations, the lifecycle, priority enum, models, policy and notifications — without forking."
url: https://roundly-consulting.com/open-source/docs/tickets-for-laravel/extending
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)

# Extension points

Swap the statuses, the priorities, the permission rules, the notifications, the assignment and due-date rules — even the models — without forking. The config-bound contracts can also be bound in the container directly:

| Contract | Methods | Default / bound from |
| --- | --- | --- |
| `TicketState` | `category(): StatusCategory` | `Enums\TicketStatus` |
| `TicketLifecycle` | `automation(Automation $hook): ?string` | `Lifecycle\TicketLifecycle (tickets.lifecycle)` |
| `Priority` | `weight(): int` | `Enums\TicketPriority (tickets.priority.enum)` |
| `Assignee` | `ticketAssigneeName(), ticketNotifiables(): iterable, isEligibleTicketAssignee(Ticket)` | Concerns\\IsTicketAssignee defaults |
| `TicketProject` | `ticketReferencePrefix(): string` | your project model |
| `AssignmentStrategy` | `assigneeFor(Ticket): ?Model` | `Assignment\CategoryDefaultStrategy (tickets.assignment.strategy)` |
| `DueDateResolver` | `dueAt(Ticket): ?CarbonImmutable` | `Due\CategoryDueDateResolver (tickets.due.resolver)` |
| `GuestLinkGenerator` | `for(Ticket, Model $recipient): string` | `Guests\SignedRouteLinkGenerator (tickets.guests.link_generator)` |
| `AgentScope` | `allows(Model $agent, Ticket), constrain(Builder, Model $agent)` | `Authorization\UnrestrictedAgentScope (tickets.authorization.agent_scope)` |
| `ConversationLock` | `lock, unlock, isLocked` | `Conversation\CommentsConversationLock` |
| `TicketEvent` | `ticket(): Ticket, actor(): ?Model` | implemented by every ticket event (see Events) |

At creation every contract sees the stored ticket: the assignee (an eligibility check through isEligibleTicketAssignee() → AgentScope::allows(), or AssignmentStrategy::assigneeFor()) and the due date (DueDateResolver::dueAt()) are picked right after the row is inserted, in the same transaction — with its key, reference, number, initial status, requester, project, category (loaded), priority and channel, but no custom field values, tags, watchers, attachments, assignee or due date yet. An allows() that asks the database by key works.

## The shipped implementations

| Class | Implements | Behaviour |
| --- | --- | --- |
| `Assignment\CategoryDefaultStrategy` | `AssignmentStrategy` | The default\_assignee of the ticket’s category, or of its nearest ancestor that sets one. |
| `Due\CategoryDueDateResolver` | `DueDateResolver` | Now plus the default\_due\_in of the ticket’s category, or of its nearest ancestor that sets one. |
| `Guests\SignedRouteLinkGenerator` | `GuestLinkGenerator` | A temporary signed URL of tickets.guest.show; needs the guest routes, else InvalidTicketsConfigurationException. |
| `Authorization\UnrestrictedAgentScope` | `AgentScope` | Every agent works on every ticket. |
| `Conversation\CommentsConversationLock` | `ConversationLock` | Comments::lock() / unlock() on the ticket’s thread, idempotent both ways (bound with $app->bind(), so rebind it to replace it). |
| `Lifecycle\TicketLifecycle` | `TicketLifecycle` | The shipped lifecycle (see Statuses and transitions). |
| `Policies\TicketPolicy` | — | The API’s policy (see Policy and agent scope). |

Each is non-final where a subclass makes sense (TicketLifecycle, TicketPolicy, the notification classes); otherwise implement the contract yourself.

## Other swap points

- The lifecycle — tickets.lifecycle (see Statuses and transitions).
- The priority scale — tickets.priority.enum (see Categories and priorities).
- The models — tickets.models.\* with subclasses of the packaged models (see Models, keys and references).
- The policy — tickets.authorization.policy with a TicketPolicy subclass (see Policy and agent scope).
- Each notification — tickets.notifications.types.<type> with a subclass, or false to switch it off (see Notifications).
- Actions — bind your own implementation in the container; every facade path resolves actions from it.

## Public support classes

| Class | Methods | Use |
| --- | --- | --- |
| `Support\TicketModels` | `ticket(), category(), field(), tag(), guest()` | The model seam — the configured class of tickets.models.\*. Query through it in host code that must follow a model swap (TicketModels::ticket()::query()). |
| `Support\Optional` | `new Optional, static given(mixed $value): bool` | The not-given sentinel of the Update\*Data DTOs, so null can mean clear. |
| `Support\TicketsConfig` | `lifecycle(), priorityEnum(), referencePrefix(), channels(), autoCloseAfter(), notificationUrl($reference), routesEnabled(), …` | Read a tickets.\* setting exactly as the package does — a junk value throws naming the key. |
| `Fields\FieldSetResolver` | `forTicket(Ticket), forScope(…), attributeDefinitions(Ticket), knownKeys(Ticket), flush()` | The applicable field set, memoised per (project, category); a scoped binding (per request / job, Octane-safe) — resolve it from the container, never new it. |
| `Notifications\RecipientResolver` | `created(), assigned(), replied(), noteAdded(), mentioned(), statusChanged(), due(), merged(), replyType(AuthorRole)` | Who each notification goes to, already visibility-checked, deduplicated and filtered — reuse it to fan a ticket change out on your own channel. |

Everything else under Support\\, Http\\, Listeners\\ and Actions\\Internal\\ is internal.

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