> ## Documentation Index
> Fetch the complete documentation index at: https://docs.azvian.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customizing with Hooks

> Add your own logic without editing a single core file

You can add your own logic to this CRM — send a Slack message when a lead converts, sync invoices to your accounting system, tweak a PDF template — without editing any core file. That means your changes survive every future update: you just drop your code into one file, and updates never touch it.

## Where your code goes

Everything lives in **`app/Hooks/custom.php`**. It ships blank. Add functions and register them with `add_action()` or `add_filter()`, the same pattern used by WordPress and Perfex — if you've customized either of those before, this will feel familiar.

```php theme={null}
<?php

use App\Models\Lead;

add_action('lead_converted', function (Lead $lead, $customer) {
    Log::info("Lead {$lead->name} converted to customer #{$customer->id}");
});
```

If your callback throws an exception, it's caught and logged — it will never break the action that triggered it (a broken Slack integration won't stop a real lead from converting).

## Actions — do something after an event

| Hook                 | Fires when                                                                                              | Arguments                        |
| -------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `ticket_opened`      | A new support ticket is created (by a client, or by inbound email if you've set up mailbox integration) | `Ticket $ticket`                 |
| `ticket_replied`     | Anyone replies to a ticket                                                                              | `TicketReply $reply`             |
| `lead_converted`     | A lead is converted into a customer                                                                     | `Lead $lead, Customer $customer` |
| `invoice_sent`       | An invoice is sent to a customer                                                                        | `Invoice $invoice`               |
| `invoice_cancelled`  | An invoice is cancelled                                                                                 | `Invoice $invoice`               |
| `payment_recorded`   | A payment is recorded — manually by staff, or automatically via a Stripe webhook                        | `Payment $payment`               |
| `proposal_responded` | A customer accepts or declines a proposal                                                               | `Proposal $proposal`             |
| `estimate_responded` | A customer accepts or declines an estimate                                                              | `Estimate $estimate`             |
| `chat_message_sent`  | A staff chat message is sent (requires the [Staff Chat add-on](/crm/addons))                            | `ChatMessage $message`           |

## Filters — change a value

Filters let you swap out a value the app is about to use. Return your own value from the callback:

```php theme={null}
add_filter('pdf_view_invoice', function (string $view, $invoice) {
    return $invoice->customer->currency === 'EUR' ? 'pdfs.invoice-eu' : $view;
});
```

| Hook                | Changes                                  | Arguments passed to your callback  |
| ------------------- | ---------------------------------------- | ---------------------------------- |
| `pdf_view_invoice`  | Which Blade view renders an invoice PDF  | `string $view, Invoice $invoice`   |
| `pdf_view_estimate` | Which Blade view renders an estimate PDF | `string $view, Estimate $estimate` |
| `pdf_view_proposal` | Which Blade view renders a proposal PDF  | `string $view, Proposal $proposal` |

A worked example: put your own Blade file at `resources/views/pdfs/invoice-eu.blade.php` and use it only for EUR customers, without ever touching the stock `pdfs/invoice.blade.php`.

<Note>
  The screens you see in the browser (Kanban board, dashboard, reports, everything) are a compiled React app, not server-rendered Blade — there's no template-override mechanism for them the way there is for PDFs. You have the full `resources/js/` source in your copy, so you're always free to edit a page directly; there just isn't a drop-in-override layer for it like `custom.php` gives you for backend logic. If you do edit `resources/js/`, run `npm run build` afterward and keep a note of what you changed before installing a future update.
</Note>

## Text and translations

Every user-facing string in Laravel's own auth screens (login, password reset, etc.) can be overridden without touching a template, via `lang/en/*.php`. If those files don't exist yet in your copy, generate them with:

```bash theme={null}
php artisan lang:publish
```

Laravel checks your `lang/` directory first, so anything you put there wins.
