venturedrake/laravel-crm
Laravel CRM is a free, open-source, self-hosted CRM package for Laravel applications. It provides leads, contacts, accounts, deals, activities, notes, and customizable sales pipelines, plus modern Laravel authentication and Blade UI components to help you add CRM functionality quickly and easily.
Baseline. This supersedes the initial 2.4.7 assessment, which was established from a single patch release note with correspondingly limited confidence. 2.5.0 is a materially larger release — a new product-variations feature spanning schema, services, PDFs, email, portal, Xero and REST API, plus four behavioral fixes and two removals. Confidence in the change description is now higher (the notes enumerate schema, services, tests and edge cases in unusual detail), but confidence in the package as a whole remains bounded: we still have no full package review, no changelog beyond per-release notes, and no visibility into CI.
Architecture fit. Unchanged in shape, and the shape continues to hold. The package remains a conventional Laravel domain module: publishable config, service provider, laravelcrm:update Artisan command, settings middleware keyed on crm.settings-seeded.<version>, Livewire components for forms. 2.5.0 introduces no new architectural primitives — it extends existing ones:
crm_product_variations, crm_product_prices.product_variation_id and the product_variation_id line columns already existed but were never read or written. 2.5.0 turns them on and adds two idempotent database/updates migrations for code, barcode, order, active, user_*_id, plus purchase-order line columns. This is a favorable pattern (additive migrations over a schema already anticipated) but it means a previously inert part of the database now becomes load-bearing.ResolvesProductVariation, and pricing in Product::unitPriceFor(), with presentation split across displayName() / displayCode() on line models. That is the right factoring for a TPM to note: the integrity rules live in one service and one model method rather than being duplicated across five line-item types and five PDF styles.XeroLineItem now composes variation name into Description and conditionally substitutes the variation SKU as ItemCode. This is the only place 2.5.0 reaches outside the app's own data model with new semantics.config/package.php still read 2.4.7 — the exact defect recorded in our 2.4.7 assessment recurred on the very next release.Integration feasibility. Medium, down from High. Still no new Composer dependencies and no changes required to host-application code, but this release is not a drop-in in the way 2.4.7 was:
/crm/customers routes, CustomerController, StoreClientRequest/UpdateClientRequest, the customers/index view, and the entire Product Attributes surface (ProductAttribute model, policy, controller, routes, views, two Settings nav entries, lang keys) are gone. These errored for most users (#93, #53), so end-user impact is likely near zero — but any host app that referenced laravel-crm.customers.* route names, extended CustomerController, or used the ProductAttribute model will hard-fail with class-not-found or missing-route errors. A minor version carrying removals is a semver deviation worth recording regardless of how dead the code was.Technical risk. Moderate, concentrated in data-mutation and external-sync surfaces rather than in code quality. The release is unusually well-tested — eight named feature test files covering the variation feature alone (model, two Livewire forms, deals, conversions, PDFs, Xero, API v2, migrations) plus dedicated tests for the PO schema fix, restored-user columns, and person/organization linking. That is the strongest single signal in these notes and materially lowers the risk of the new code being wrong. The residual risks are:
config/package.php again read 2.4.7 at build time. The consequences the notes spell out — banner compares against the previous release, "database behind code" check misreports, and the version-keyed seeding flag fails to re-arm — are the ones we predicted. Nothing indicates a CI guard was added. This is a process risk with a known, cheap fix that is not being applied.update() to read products instead of the never-sent purchaseOrderLines is right, and scoping matches to the PO being edited is right. But "removes lines that are no longer submitted" is a destructive default. Any caller — UI bug, API integration, or script — that submits a partial line set will now delete lines rather than silently no-op as before. The previous behavior was data loss in the other direction, so this is a net improvement, but it converts a silent failure into a destructive one.Product::productPrices() now returns product-level rows only, and ProductPrice::scopeDefault() excludes variation prices (and previously compared currency against a Setting model rather than its value). This is a genuine bug fix. Its side effect: on any install where a price row would now be classified as a variation price, getDefaultPrice() can return a different value — or nothing — after upgrade. Low likelihood, non-zero, and hard to detect without a data check.ItemCode and Description semantics shift. Sending the variation SKU as ItemCode only when a synced Xero item already carries that code is a sensible guard against creating orphan items. It also means invoice lines for the same parent product can reference two different Xero item codes depending on sync history. Anyone reconciling Xero against the CRM by item code will see new patterns.Key questions.
composer.json? This has failed on two consecutive releases; without a guard it will fail again.laravel-crm.customers.*, CustomerController, or ProductAttribute — and if so, what is the migration guidance?code/barcode (the products index now searches by variation SKU) and on product_variation_id line columns?crm_product_variations ever populated in the field, e.g. via the V1→V2 upgrade path? If so, what data surfaces now that code reads those rows for the first time?Product::productPrices(): is there a verification query or backfill to identify products whose default price changes after upgrade?Description.Stack fit. Unchanged prerequisites — Laravel 10/11/12, Livewire for the form layer, Composer installation, and a stable APP_KEY (encryption is now more central, not less: variation pricing and display logic sit on the same model layer as the encrypted fields). One addition: the database user needs DDL rights at deploy time, because this release ships migrations that must actually run. Hosts on a migration-by-CI-pipeline model should confirm the database/updates path is included in that pipeline — the prior 2.4.7 upgrade could be completed without touching schema, and this one cannot.
Migration path. Larger than 2.4.7 and should not be treated as a hotfix. Recommended order:
composer require <vendor>/laravelcrm:2.5.0, review the lockfile diff for transitive changes. Confirm nothing in the host app references the removed CustomerController, laravel-crm.customers.* route names, StoreClientRequest/UpdateClientRequest, or any ProductAttribute class.php artisan laravelcrm:update so the three migrations apply and the db_version marker advances to 2.5.0. Do this before exercising the UI — the product form and purchase-order saves will fail without the new columns.crm.settings-seeded.2.4.7 will not match 2.5.0, which is the intended re-seed. Refresh config:cache explicitly; an opcode-cached deploy can otherwise serve the old constant.productPrices() scope; (b) any pre-existing rows in crm_product_variations or product_variation_id columns that will now be rendered; (c) purchase orders currently holding lines, to confirm the new diff-and-delete update path behaves on real data.user_restored_id is set and user_deleted_id cleared → saving a lead with both a person and an organization and confirming the cross-link appears on both show pages → one Xero invoice sync with a variation line.variations key, and exercise POST/PUT/DELETE /products/{product}/variations plus the 422 path for a variation belonging to another product or team.Compatibility. Additive for the API (new sub-resource, new line_items field, new variations key on product responses) — strict schema validators on the consumer side are the only likely breakage. The removals are the compatibility edge: functionally dead code, but code-level breaking if referenced. The variation feature is explicitly scoped so that a product with no variations behaves exactly as before, which bounds regression risk for the majority of installs. The trait-hook extension point from 2.4.7 remains supported; no reversal is announced.
Sequencing. Do not bundle. 2.5.0 combines schema changes, a new feature across nine surfaces, four behavior fixes, and two removals — that is too much to attribute cleanly if something regresses. Land it as its own release, in a window where a rollback is tolerable, and stage it: migrations plus backend first in a staging environment, UI verification second, Xero sync verification last (it is the only irreversible external side effect). If the host is mid-migration on Livewire or on the encryption feature, finish that first, as with 2.4.7. Teams that have not yet taken 2.4.7 can go directly to 2.5.0 — the version marker and seeding flag advance to 2.5.0 either way, and there is no intermediate step to honor.
Maintenance. Low steady-state burden continues — no new services, jobs or infrastructure. Two standing items, one of them now overdue:
config('package.version') matches the git tag or composer.json retires this class of defect permanently. Until then, the release checklist must cover all five locations: config/package.php, SystemCheckService::normalisedVersion(), the db_version marker, the UpdateController banner, and the seeding flag.create() writing comments and order_product_id to columns no migration ever added, passing because no test saved a PO with lines — is exactly the failure a staging deploy against a real database catches. It is also a reminder that the test suite's schema and a production schema had diverged; a periodic schema-diff between test and production snapshots is cheap insurance.Support. Expect a broader and longer tail than 2.4.7, in four distinct buckets:
/crm/customers now 404s instead of throwing Call to undefined method ... sortable(). Bookmarked links get a cleaner failure, which is an improvement, but support should have a scripted "use People and Organizations instead" response ready.Person/organization linking is likely to generate positive support contact rather than negative — the link appearing on both show pages is the visible outcome of a bug users had stopped reporting.
Scaling. Modest but no longer negligible. Three things to watch:
unitPriceFor(). On documents with many lines, and on list/index pages, this is a candidate for N+1 unless variation and price relations are eager-loaded. Worth a query-count check on a representative order before and after.crm_product_variations. Confirm the migration adds an index on code (and barcode if searched) — without one this degrades as variation volume grows.Everything else — the seeding flag (per-version, not per-user), the observer fixes, the API sub-resource authorization (delegated to the parent product, so no new permission surface) — has no scaling implication.
Failure modes. Five, ordered by severity:
laravelcrm:update run. This is the highest-severity failure mode in the release and the one most likely to occur on a pipeline that doesn't execute the package's updates.crm_product_variations or product_variation_id was ever populated outside this code path, existing documents may render a variation where none was previously shown, and getDefaultPrice() may resolve differently. Detectable with the pre-upgrade data verification query; not self-healing.Ramp-up. Higher than 2.4.7 but still contained. A developer fluent in Laravel packages and Livewire needs roughly one to two days to be productive on this release: the variation model and its pricing rule, the ResolvesProductVariation integrity seam, the five line-item types and their conversions, the displayName()/displayCode() presentation split, the API sub-resource, and the Xero composition rule. Add half a day of QA orientation for the PDF/email/portal rendering paths, since those are visual and easy to under-test. The one concept a new team member must internalize is still the version-coupled chain — config constant → db_version marker → update banner → seeding flag — because it is now demonstrably the most common source of confusing "why is the banner still showing" debugging. Budget it as a 30-minute onboarding note, and treat it as a prompt to fix the root cause rather than document it a third time.
How can I help you explore Laravel packages today?