tomatophp/tomato-crm
TomatoCRM is a Laravel package from the TomatoPHP ecosystem that brings CRM capabilities to your admin panel. Manage customers, contacts, leads, deals, and sales pipelines through a ready-to-use Filament interface, with customizable resources and integrations for your workflow.
Before writing a line of code, confirm the architectural fit — it's binary. tomatophp/tomato-crm is a Filament/TomatoPHP module, not a standalone CRM library. It mounts accounts, contacts, leads, companies, deals, activities, and notifications into an existing admin panel and assumes the TomatoPHP scaffolding (tomato-admin, tomato-support, and usually the sibling roles/settings/notifications packages). If you're on plain Laravel or Filament without that stack, adopting this means adopting the whole admin framework to get a fairly small domain model — decide that consciously before you spend a sprint on it.
Inspect the real constraints first:
composer show -a tomatophp/tomato-crm
# After install, read what it actually pulled in:
cat vendor/tomatophp/tomato-crm/composer.json
Then install:
composer require tomatophp/tomato-crm
php artisan vendor:publish --tag=tomato-crm-config
php artisan vendor:publish --tag=tomato-crm-migrations
php artisan migrate
php artisan filament:optimize-clear
Restart your dev server so the Filament resources register.
Where to look first: the published migrations. They are the artifact you're actually adopting — read every table, column, foreign key, and index before other code depends on it. Then read routes/ for the API surface and the config for auth defaults. The README and release notes are thin; the code is the documentation.
First use case: open the panel, go to Accounts, create an account, attach a contact, and verify the notification fires. That single flow exercises models, relationships, and the notification path — the three things you'll build on daily. Once that works, hit the API endpoints the same way to confirm the contract before you build anything on top.
Treat Filament resources as scaffolding, not the contract. Resources are UI. The durable surface you build on day-to-day is the Eloquent models and the API routes. Anything your app relies on should go through the model layer or an HTTP client against the API — Filament page classes are the most likely thing to break on a major upgrade.
Wrap the domain in a thin service layer. Write a small CrmService that owns account creation, contact attachment, and notification dispatch. Controllers, jobs, and commands call the service, never the package models directly. This is your escape hatch when you fork or eventually replace the package.
class CrmService
{
public function createAccount(array $data): Account
{
return DB::transaction(fn () => Account::create($data));
}
public function attachContact(Account $account, array $data): Contact
{
return $account->contacts()->create($data);
}
}
Extend models via thin local subclasses. Package models live in vendor/ — don't patch them. Subclass in app/Models and bind them via the container, or override the package's model resolution if it exposes one. This is how you add tenant scopes, accessors, or audit hooks without forking immediately.
Queue the notification path. Notifications are the operational piece most likely to bite you in production. Wrap dispatches in a job so you control retries, backoff, and failures without touching package internals:
dispatch(new SendCrmNotification($notification, $recipient));
Test the API contract yourself. There's no public evidence of this API running in anyone else's production. Capture the endpoints you expose in your own feature tests — auth, pagination shape, filtering, error codes. Those tests are what protect you when you update or refactor.
it('lists contacts for the authenticated account', function () {
$this->actingAs($user)
->getJson('/api/contacts')
->assertOk()
->assertJsonStructure(['data' => [['id', 'name', 'email']]]);
});
Vendor or fork in week one, not month six. With zero dependents and a stale release cadence, decide your fork strategy early. Set up an internal package or a repositories path entry pointing at a forked copy, give it a named owner, and document a divergence policy. Cheap up front, expensive to retrofit.
Pin the family. tomatophp/* siblings release independently and irregularly. Use exact constraints for the evaluation window and commit the resulting composer.lock as your baseline.
Release notes are empty. Every v1.1.x bump is a compare URL and nothing else. Treat updates as opaque: install into a branch, diff the vendor tree, run your tests, only then merge. Open the compare URL when you need a file-level summary.
Brownfield migration collisions. Published migrations may collide with existing users, accounts, or notification tables. Diff the published migrations against your schema before running php artisan migrate — a partial migration state here is painful to unwind.
Verify the API auth model by reading routes. The description says "API" but doesn't specify Sanctum, scopes, pagination, or rate limiting. Open routes/api.php in the vendor tree and confirm before exposing anything. Under-configured auth on a contacts endpoint is a PII leak.
Multi-tenancy is unconfirmed. Assume single-tenant unless the migrations prove otherwise. If you need tenant scoping, add it via a global scope on your model subclass and verify the Filament resources honor it.
Soft deletes and audit trails are not a given. A CRM's value is largely history. Check the migrations for deleted_at and any audit/log table. If activity history is missing, you're building it — budget accordingly.
N+1 on contact listings. CRM tables grow fast and contact listings are the classic N+1 trap. Eager-load relationships in your service layer and measure query counts on the API before shipping. Don't assume pagination defaults are sensible.
Filament upgrades are your breakage point. The resource layer is where major-version Filament changes land. Test the panel behind a branch before upgrading, and set a calendar reminder around Filament's release cycle.
Debugging tips. If resources don't appear, check the service provider registered and tomato-admin is in your panel provider's plugin list. If API endpoints 404, confirm the route file is loaded — routes may register conditionally. If notifications silently drop, check the queue driver and whether the notification class implements ShouldQueue.
Escape hatch planning. Keep a short note on what leaving would take: which tables are the package's, which models you subclass, which endpoints your service wraps. This makes the eventual fork-or-replace decision concrete rather than emotional.
How can I help you explore Laravel packages today?