v3.0.0-beta.5
Pre-releaseCaution
This is a beta release of the framework. Breaking changes may be introduced to v3 releases during the beta period.
This fifth 3.0 beta adds bulk product import and reworks how a product is published to sales channels. It also closes a run of admin UX gaps: variant ordering that never wrote its position, product thumbnails that needed a second upload, and customers without a role going invisible on their own orders. The official 3.0 release is coming this September.
Installation
"minimum-stability": "beta",
"prefer-stable": truecomposer require shopper/framework:^3.0.0-betaHighlights
Product import pipeline
Products can now be imported in bulk instead of created one at a time. A new ImportManager singleton resolves pluggable ImportSource implementations, mirroring the ChannelManager driver pattern, so the upcoming migrator addon can register WooCommerce, PrestaShop and other platform sources with ImportManager::extend() without touching core:
interface ImportSource
{
public function code(): string;
public function name(): string;
public function description(): string;
public function icon(): string;
public function isConfigured(): bool;
/**
* @return LazyCollection<int, ProductRow>
*/
public function read(string $path): LazyCollection;
}The built-in CsvSource reads the canonical Shopper product template with league/csv, groups flat rows by handle into a ProductRow DTO (variants, options, images, categories, tags), and accepts a stored column mapping through SupportsColumnMapping so a file with different headers still lines up with Shopper's fields. ProductRowImporter then writes each product idempotently (matched by slug, variants by SKU, brands and categories resolved or created by name), and downloads images in separate queued jobs so one dead URL never fails a whole product. StartProductImport reads the source once, splits the rows into Bus::batch() chunks of shopper.core.import.chunk_size rows (25 by default), tracks progress on a new ProductImport model, and notifies administrators with the final counts when the batch completes. In the admin, Products > Import opens a three step wizard: upload, column mapping with auto-matched headers, then a review step with parsed totals.
Product overview redesign and sales channel toggles
The product overview page replaces the sales channels multi-select in Associations with a dedicated toggle list: one row per channel showing its driver logo through ChannelManager::logoFor(), the channel name, and a native Filament toggle, with a live "Available in X of Y sales channels" counter.
class ChannelToggles extends CheckboxList
{
protected function setUp(): void
{
parent::setUp();
$this->disableOptionWhen(
fn (string $value): bool => ! $this->getChannels()->firstWhere('id', (int) $value)?->is_enabled
);
}
/**
* Disabled channels stay valid so existing attachments survive a save.
*/
public function getInValidationRuleValues(): ?array
{
return $this->getChannels()->pluck('id')->map(fn ($id): string => (string) $id)->all();
}
}Filament's CheckboxList validates against enabled options only by default, which would silently drop a product's channel the moment that channel gets disabled. ChannelToggles overrides getInValidationRuleValues() to keep every channel id valid, so a disabled channel shows a locked toggle but keeps its existing attachments on save. The products table also gained a sales channel filter.
New Features
feat(i18n): add Swedish translations by @adevade in #633feat(core): product import pipeline with CSV source and column mapping by @mckenziearts in #629feat(admin): redesign the product overview page with sales channel toggles by @mckenziearts in #628feat(admin): product thumbnail gallery fallback and use-as-thumbnail picker by @mckenziearts in #627feat(admin): variant reordering, product page titles and breadcrumbs by @mckenziearts in #625
Bug Fixes
fix(admin): show customers without roles and assign customer role on order by @mckenziearts in #632fix(core): product import follow-ups after #629 by @mckenziearts in #631
Upgrading
Run your migrations. This release adds a product_imports table that tracks import progress and counts:
php artisan migratePublished product.php component config. mergeConfigFrom() merges shallowly at the components key, so a config/shopper/components/product.php published before this release (php artisan shopper:component:publish product) keeps its own stale components array and drops the two entries the new import feature needs. Add them by hand, or republish the file:
'components' => [
// ...
'products.import-progress' => Components\Products\ImportProgress::class,
'slide-overs.import-csv' => Livewire\SlideOvers\ImportCsv::class,
],Without them, opening Products > Import throws Livewire\Exceptions\ComponentNotFoundException.
Behavior changes to review
- Variants now have a real default order.
Product::variants()is ordered bypositioninstead of an unspecified row order. Existing variants were never assigned one and all sit at1, so nothing visibly changes until a merchant drags a variant or a new one is created, which now appends at the end. - The customers scope now includes roleless users.
User::customers()used to require theuserrole exactly. It now returns anyone who does not hold the admin or manager role, plus anyone holding theuserrole, so a buyer created without a role shows up in Customers and on their order instead of 404ing. New orders now assign theuserrole to their customer automatically. - The Store API
thumbnailfield now falls back to the first gallery image. Products and variants without a dedicated thumbnail used to serializethumbnail: null; they now return the first gallery image, matching what the admin already displayed. Storefronts checking for "no image" should check theimagesarray instead ofthumbnail === null.
Contributors
Full Changelog: v3.0.0-beta.4...v3.0.0-beta.5