Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Crm Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Require the package and publish its assets: composer require → php artisan vendor:publish for the CRM provider's config/migrations. The config lands at config/package.php.
  2. Confirm the published config reads 2.5.0. If you carry your own copy, bump its version key in the same commit — see the version note below.
  3. Run php artisan laravelcrm:update. This applies migrations and writes the db_version marker so SystemCheckService::normalisedVersion() stops reporting "database behind code". On 2.5.0 it also runs the two database/updates migrations that add the product-variation code, barcode, order, active and user_*_id columns and the crm_purchase_order_lines.comments / order_product_id columns. Both are idempotent, so re-running is safe.
  4. First use case — a "link a contact" Livewire form: add HasPersonSuggest / HasOrganizationSuggest to the component, bind wire:model to personName / organizationName, and render the suggestion dropdown. Picking a suggestion sets person_id / organization_id; typing a different name clears it so saving creates a new record. New in 2.5.0: saving with both a person and an organization now links them as related contacts automatically (PersonService::linkToOrganization()), so each shows the other on its show page without extra work.

Look first at config/package.php (version string + encrypt_db_fields), then SystemCheckService, UpdateController, and the Settings middleware flag crm.settings-seeded.<version> — all four read the same version value. For the new feature, read ProductVariation, ProductVariationObserver, ResolvesProductVariation, and Product::unitPriceFor().

Implementation Patterns

  • Version-gated behaviour. If you publish or override config/package.php, every package upgrade needs the version string bumped there too. Missing the bump keeps the update banner hidden, keeps the system check comparing against the previous release, and — most importantly — leaves the crm.settings-seeded.<version> middleware flag already set, so the seeding pass never re-arms on deploy. 2.5.0 is a minor release precisely because it adds a feature and an API sub-resource; don't downgrade the number to a patch on the assumption it's fixes-only. Deploy order: publish/merge config → php artisan laravelcrm:update → config:cache.
  • Product variations on line items. After a product with active variations is chosen, render a variation select next to the product select. Picking one prices the line via Product::unitPriceFor() — variation price in the document currency, falling back to the product price when the variation has no price there. Changing the product must clear the variation. Only persist a variation that belongs to that line's product in the current team (ResolvesProductVariation enforces this); the REST API returns 422 for a cross-product or cross-team variation, so mirror that validation in custom forms to avoid a silent drop.
  • Variation lifecycle. Removing a variation soft-deletes it, which is what lets existing lines keep their label and lets conversions carry the variation even after deletion. Never hard-delete variations. Reordering uses the order column; deactivating is the "hide from the picker but keep history" lever. ProductVariationObserver handles external_id and the user_*_id columns — don't set them by hand.
  • Display and Xero. Line models expose displayName() (Product — Variation) and displayCode() (variation SKU, else product SKU); all five PDF styles and the emailed/portal documents reuse those views, so custom documents should call the same helpers rather than formatting names inline. In Xero, Description gets the variation name and ItemCode is the variation SKU only if a synced Xero item already has that code — otherwise the parent product's item code (src/Support/XeroLineItem.php). Variations are never created as Xero items.
  • Select-then-rename flow. Keep the trait properties as the source of truth for the autocomplete. After a suggestion is picked, editing the name triggers the trait's own updatedHasPersonSuggest() / updatedHasOrganizationSuggest() hook, which compares the trimmed name to the decrypted linked record and nulls the FK. That's what makes "Paul Clark" → "Paul" create a new person instead of silently re-attaching Paul Clark. Note the 2.5.0 person↔organization link fix does not duplicate an existing link, and changing the person or organization on a record keeps the old link — so don't try to "clean up" links on edit.
  • Custom hooks are safe now. You can still define updatedPersonName() / updatedOrganizationName() for normalisation, validation, or slug generation — the trait hooks are scoped to their own properties and fire independently. Just don't let your hook re-assign the FK after the trait has cleared it.
  • Purchase-order lines. PurchaseOrderService::update() now reads $request->products using the same row keys as create(); it updates matched lines, adds new ones, and removes lines no longer submitted, matching only lines belonging to the order being edited. Any custom PO form, Livewire component, or API wrapper must post products, not purchaseOrderLines, or edits will be silently lost.
  • Encryption. With encrypt_db_fields on, name comparisons must go through the decrypted model accessor. The trait already does this; any custom suggestion query or duplicate check you write must do the same rather than comparing raw ciphertext columns.
  • Testing. Mirror the closest feature test for whatever you touch: ContactSuggestSearchTest (select→rename leaves person_id null, "New" badge reappears, saving persists a fresh record), DealProductVariationTest / ProductVariationConversionTest (variation pricing and carry-through), PurchaseOrderService coverage via the PO tests, XeroLineItemTest, and Api/V2/ProductVariationApiTest.

Gotchas and Tips

  • The config filename is literally config/package.php. It's easy to miss when diffing upgrades — and its version key drives the update banner, the system check, the db_version marker, and the settings-seeding flag simultaneously.
  • php artisan config:cache during deploy will happily cache a stale version string. Always re-run it after publishing the new config, or the update prompt and seeding pass stay suppressed.
  • Variation prices leaking into product prices. crm_product_prices holds both product-level and variation-level rows. As of 2.5.0, Product::productPrices() returns product-level rows only and ProductPrice::scopeDefault() excludes variation prices (and now compares the currency against the Setting value, not the model). If you wrote your own "first matching price row" query before this release, it has been quietly returning a variation price — switch to productPrices(), scopeDefault(), or unitPriceFor().
  • Removed v1 /customers routes. The laravel-crm.customers.* route group, CustomerController, the customers/index view, and StoreClientRequest / UpdateClientRequest are gone. Any menu entry, redirect, or hard-coded route('laravel-crm.customers.index') in your app will now throw. The Customer model, observer, and policy stay for the V1→V2 upgrade path and the customer_id columns on leads/deals/quotes/orders — use People and Organizations for new UI work. Covered by CustomersRoutesRemovedTest.php.
  • Product Attributes settings page removed. Routes, controller, model, policy, view, both Settings nav entries and the unused product_attribute* lang keys are gone. Custom nav or settings links to it will break.
  • Restored records showing the deleter. ProductObserver, LunchObserver, and FileObserver used to write the restoring user into user_deleted_id. They now clear user_deleted_id and set user_restored_id. If you copied that observer pattern into your own models, you have the same bug — check every restored() you own.
  • Purchase orders on a real database. PurchaseOrderService::create() wrote comments and order_product_id with no migration adding them; the suite passed only because no test saved a PO with lines. If you skipped laravelcrm:update after upgrading, PO saves with lines will still fail — run the command, don't assume the schema is current.
  • Trait hooks are bound to the property name (hasPersonSuggest, hasOrganizationSuggest). Renaming that bound property, or moving the name into a nested/array property, silently disables link invalidation and the stale-link bug returns.
  • The comparison uses the trimmed name, so whitespace-only edits correctly keep the link. Case-only edits ("paul" vs "Paul") are worth verifying against the feature test before you rely on either outcome.
  • If you override the traits or the components that use them, keep the id properties and the hasXSuggest properties in sync — setting person_id after the trait clears it re-creates the original attach-the-wrong-record bug. Same rule for product_variation_id: clearing it on product change is load-bearing, not cosmetic.
  • API consumers: product responses now include variations, and products/{product}/variations is authorised against the parent product (no new permission needed). Quote, order, and invoice line_items accept product_variation_id. Check your client's response parsing tolerates the extra key.
  • Extension point: subclassing the traits and adding your own updated* methods is supported; the risk is ordering, so re-read the id properties at save time rather than trusting values set mid-request. Same for variations — resolve the variation through ResolvesProductVariation at save time rather than in the request handler.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource