{"id":50218824,"url":"https://github.com/run-as-root/magento-2-seeder","last_synced_at":"2026-06-15T12:30:57.857Z","repository":{"id":352383646,"uuid":"1210378898","full_name":"run-as-root/magento-2-seeder","owner":"run-as-root","description":"Laravel-style database seeder for Magento 2 / Mage-OS. Generate realistic products (all types), categories, customers, orders (all states), CMS pages, and reviews via bin/magento db:seed.","archived":false,"fork":false,"pushed_at":"2026-04-19T11:05:27.000Z","size":355,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T11:17:46.414Z","etag":null,"topics":["dev-tools","faker","fixtures","mage-os","magento","magento-module","magento2","php","seeder","seeding","test-data"],"latest_commit_sha":null,"homepage":"https://github.com/run-as-root/magento-2-seeder","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/run-as-root.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-14T11:05:09.000Z","updated_at":"2026-04-19T10:31:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/run-as-root/magento-2-seeder","commit_stats":null,"previous_names":["run-as-root/magento-2-seeder"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/run-as-root/magento-2-seeder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-as-root%2Fmagento-2-seeder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-as-root%2Fmagento-2-seeder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-as-root%2Fmagento-2-seeder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-as-root%2Fmagento-2-seeder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/run-as-root","download_url":"https://codeload.github.com/run-as-root/magento-2-seeder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run-as-root%2Fmagento-2-seeder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34225351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dev-tools","faker","fixtures","mage-os","magento","magento-module","magento2","php","seeder","seeding","test-data"],"created_at":"2026-05-26T11:00:20.986Z","updated_at":"2026-06-12T02:00:59.222Z","avatar_url":"https://github.com/run-as-root.png","language":"PHP","funding_links":[],"categories":["Open Source Extensions"],"sub_categories":["CMS"],"readme":"# Magento 2 Database Seeder\n\n[![CI](https://github.com/run-as-root/magento-2-seeder/actions/workflows/ci.yml/badge.svg)](https://github.com/run-as-root/magento-2-seeder/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nLaravel-style database seeding for Magento 2 / Mage-OS. Define simple PHP / JSON / YAML files (or use the built-in Faker generators), run `bin/magento db:seed`, populate your dev environment with realistic products, categories, customers, orders, CMS content, reviews, cart rules, wishlists, and newsletter subscribers.\n\n![db:seed --generate=order:10 --fresh](docs/demo/seed-orders.gif)\n\n## Installation\n\n```bash\ncomposer require runasroot/module-seeder --dev\nbin/magento module:enable RunAsRoot_Seeder\nbin/magento setup:upgrade\n```\n\n## Quick Start\n\n1. Scaffold a seeder: `bin/magento db:seed:make`\n2. Run `bin/magento db:seed`\n\n## Usage\n\n```bash\n# Run all seeders\nbin/magento db:seed\n\n# Run only specific types\nbin/magento db:seed --only=customer,order\n\n# Skip specific types\nbin/magento db:seed --exclude=cms\n\n# Wipe relevant data and re-seed\nbin/magento db:seed --fresh\n\n# Stop on first error\nbin/magento db:seed --stop-on-error\n\n# Combine flags\nbin/magento db:seed --fresh --only=customer,product\n\n# Show current DB counts of seeded entities\nbin/magento db:seed:status\n```\n\n## Scaffolding\n\n`db:seed:make` creates one or more seeder files for you — no need to memorize the format.\n\n```bash\n# Interactive — multi-select entity types, per-type counts, one file per type.\n# Labels show cascade hints, e.g. \"order — cascades: customer, product, category\".\nbin/magento db:seed:make\n\n# Flag-driven (CI / scripts) — single type per invocation.\nbin/magento db:seed:make --type=order --count=100 --format=php\n\n# Overwrite an existing file\nbin/magento db:seed:make --type=order --count=100 --force\n```\n\nInteractive mode pairs a multi-select (use space to toggle, enter to confirm) with a\nper-type count prompt — picking `order`, `customer` writes both `OrderSeeder.\u003cext\u003e`\nand `CustomerSeeder.\u003cext\u003e`. Shared prompts (locale, seed, format) apply to every\nfile written in the same run. If any target already exists you'll get a per-file\noverwrite confirm; declined files are skipped and the rest continue.\n\nFlag-driven mode stays single-type (`--type=X --count=N`); multi-type via flags\nis not supported yet.\n\nAvailable flags:\n\n| Flag | Default | Notes |\n|------|---------|-------|\n| `--type` | — | required non-interactive |\n| `--count` | — | required non-interactive |\n| `--format` | `php` | `php` / `json` / `yaml` |\n| `--name` | `{Type}Seeder` | file name without extension |\n| `--locale` | `en_US` | Faker locale |\n| `--seed` | random | Faker seed for deterministic output |\n| `--force` | `false` | overwrite existing file |\n\n## Seeder Formats\n\nSeeder files live in `dev/seeders/` and must end in `Seeder.\u003cext\u003e`. Three formats are supported:\n\n| Format | Extension         | Use when                                            |\n|--------|-------------------|-----------------------------------------------------|\n| PHP    | `.php`            | Array or class with loops / Faker / conditionals    |\n| JSON   | `.json`           | Machine-generated fixtures or cross-language tools  |\n| YAML   | `.yaml` / `.yml`  | Human-readable fixtures                             |\n\nAll three share the same payload shape: `type`, `data` (or `count`), and optional `order`/`locale`/`seed`.\n\n### Array-Based (PHP)\n\nCreate a PHP file ending in `Seeder.php` that returns an array with `type` and `data`:\n\n```php\n\u003c?php\n// dev/seeders/CustomerSeeder.php\nreturn [\n    'type' =\u003e 'customer',\n    'data' =\u003e [\n        ['email' =\u003e 'john@test.com', 'firstname' =\u003e 'John', 'lastname' =\u003e 'Doe', 'password' =\u003e 'Test1234!'],\n        ['email' =\u003e 'jane@test.com', 'firstname' =\u003e 'Jane', 'lastname' =\u003e 'Doe', 'password' =\u003e 'Test1234!'],\n    ],\n];\n```\n\n### JSON\n\n```json\n// dev/seeders/CustomerSeeder.json\n{\n    \"type\": \"customer\",\n    \"data\": [\n        {\"email\": \"john@test.com\", \"firstname\": \"John\", \"lastname\": \"Doe\", \"password\": \"Test1234!\"},\n        {\"email\": \"jane@test.com\", \"firstname\": \"Jane\", \"lastname\": \"Doe\", \"password\": \"Test1234!\"}\n    ]\n}\n```\n\n### YAML\n\n```yaml\n# dev/seeders/CustomerSeeder.yaml\ntype: customer\ndata:\n  - email: john@test.com\n    firstname: John\n    lastname: Doe\n    password: Test1234!\n  - email: jane@test.com\n    firstname: Jane\n    lastname: Doe\n    password: Test1234!\n```\n\nInvalid JSON/YAML files are logged to `var/log/` and skipped; the rest of the run continues.\n\n### Class-Based (fluent, recommended)\n\nFor complex scenarios, extend `RunAsRoot\\Seeder\\Seeder` and use the fluent builder:\n\n```php\n\u003c?php\n// dev/seeders/MassOrderSeeder.php\nuse RunAsRoot\\Seeder\\Seeder;\n\nclass MassOrderSeeder extends Seeder\n{\n    public function getType(): string { return 'order'; }\n    public function getOrder(): int { return 40; }\n\n    public function run(): void\n    {\n        $this-\u003eorders()\n            -\u003ecount(50)\n            -\u003ewith(['items' =\u003e [['sku' =\u003e 'TSHIRT-001', 'qty' =\u003e 2]]])\n            -\u003ecreate();\n    }\n}\n```\n\nAvailable builder entry points: `customers()`, `products()`, `orders()`, `categories()`, `cms()`, plus `seed('custom_type')` for types registered via `di.xml`.\n\nBuilder methods:\n\n| Method | Purpose |\n|---|---|\n| `-\u003ecount(int $n)` | How many to create |\n| `-\u003ewith(array $data)` | Static overrides merged into each iteration (shallow replace) |\n| `-\u003eusing(callable $fn)` | Per-iteration callback: `fn(int $i, Faker\\Generator $faker): array` |\n| `-\u003esubtype(string $s)` | Force subtype (e.g. `'bundle'` for products, `'complete'` for orders) |\n| `-\u003ecreate()` | Executes and returns `int[]` of created ids |\n\nPrecedence (most specific wins): `using()` \u003e `with()` \u003e generator Faker defaults.\n\nIf your subclass needs its own dependencies, override the constructor and call `parent::__construct(...)`:\n\n```php\npublic function __construct(\n    EntityHandlerPool $handlers,\n    DataGeneratorPool $generators,\n    FakerFactory $fakerFactory,\n    GeneratedDataRegistry $registry,\n    private readonly MyService $svc,\n) {\n    parent::__construct($handlers, $generators, $fakerFactory, $registry);\n}\n```\n\n### Class-Based (low-level)\n\nIf you need full control, implement `SeederInterface` directly and inject `EntityHandlerPool`:\n\n```php\nclass CustomSeeder implements SeederInterface\n{\n    public function __construct(private readonly EntityHandlerPool $handlerPool) {}\n    public function getType(): string { return 'order'; }\n    public function getOrder(): int { return 40; }\n    public function run(): void\n    {\n        $this-\u003ehandlerPool-\u003eget('order')-\u003ecreate([...]);\n    }\n}\n```\n\n## Supported Entity Types\n\n| Type                    | What it creates                                        |\n|-------------------------|--------------------------------------------------------|\n| `customer`              | Customer accounts                                      |\n| `category`              | Category tree nodes                                    |\n| `product`               | Products (all five Magento types)                      |\n| `order`                 | Orders via quote-to-order flow                         |\n| `cms`                   | CMS pages and blocks                                   |\n| `cart_rule`             | Shopping-cart price rules with a specific coupon each  |\n| `wishlist`              | Wishlists with 1–5 product items per seeded customer   |\n| `newsletter_subscriber` | Newsletter subscribers (50/50 linked customers vs guests) |\n\n## Default Seeding Order\n\n1. Categories (10)\n2. Products (20)\n3. Customers (30)\n4. Orders (40)\n5. CMS / Cart Rules (50)\n6. Wishlists (60)\n7. Newsletter Subscribers (70)\n\nOverride with `'order' =\u003e 5` in array seeders or `getOrder(): int` in class seeders.\n\n## The `--fresh` Flag\n\nWhen using `--fresh`, the module cleans existing data before seeding:\n\n- **Customers**: Deletes all non-admin customers\n- **Products**: Deletes all products\n- **Categories**: Deletes all categories except root (ID 1) and default (ID 2)\n- **Orders**: Deletes all orders (FK cascades handle invoices, shipments, etc.)\n- **CMS**: Only deletes pages/blocks with the `seed-` identifier prefix\n- **Cart Rules**: Only deletes rules whose name starts with `Seed Rule — ` (attached coupons cascade)\n- **Wishlists**: Only deletes wishlists whose owner's email matches `%@example.%` (items cascade via FK)\n- **Newsletter Subscribers**: Only deletes rows whose email matches `%@example.%`\n\nClean runs in reverse dependency order (later-ordered types first).\n\n## Extending\n\nAdd custom entity handlers via `di.xml`:\n\n```xml\n\u003ctype name=\"RunAsRoot\\Seeder\\Service\\EntityHandlerPool\"\u003e\n    \u003carguments\u003e\n        \u003cargument name=\"handlers\" xsi:type=\"array\"\u003e\n            \u003citem name=\"custom_entity\" xsi:type=\"object\"\u003eVendor\\Module\\Seeder\\CustomEntityHandler\u003c/item\u003e\n        \u003c/argument\u003e\n    \u003c/arguments\u003e\n\u003c/type\u003e\n```\n\nYour handler must implement `RunAsRoot\\Seeder\\Api\\EntityHandlerInterface`.\n\n## Data Generation with Faker\n\nGenerate realistic fake data at scale using the `--generate` flag. No seeder files needed.\n\n### Basic Usage\n\n```bash\n# Generate 1000 orders\nbin/magento db:seed --generate=order:1000\n\n# Generate multiple entity types\nbin/magento db:seed --generate=order:1000,customer:500\n\n# Use a specific locale\nbin/magento db:seed --generate=customer:100 --locale=de_DE\n\n# Deterministic output (same seed = same data)\nbin/magento db:seed --generate=product:50 --seed=42\n\n# Combine with --fresh to wipe and regenerate\nbin/magento db:seed --generate=order:500 --fresh\n```\n\n### Progress bar\n\nWhen a resolved count for any type is **10 or more**, the command renders a per-type\nSymfony Console progress bar so long runs show live progress. Smaller counts keep the\ncompact `Generated N type(s)... done` output. Nothing to configure.\n\n### Smart Dependency Resolution\n\nYou only need to request the entities you want. Dependencies are auto-generated with sensible ratios.\n\nFor example, `--generate=order:1000` will also generate the required customers, products, and categories automatically.\n\n| Requested                    | Auto-generates                                                                          |\n|------------------------------|-----------------------------------------------------------------------------------------|\n| `order:1000`                 | `customer:200` (1:5 ratio), `product:50` (1:20 ratio), `category:10` (1:5 of products)  |\n| `product:100`                | `category:20` (1:5 ratio)                                                               |\n| `wishlist:50`                | `customer:50` (1:1 ratio); products reused from whatever's in the DB/registry           |\n| `customer:500`               | Nothing (no dependencies)                                                               |\n| `category:50`                | Nothing (no dependencies)                                                               |\n| `cms:20`                     | Nothing (no dependencies)                                                               |\n| `cart_rule:20`               | Nothing (no dependencies)                                                               |\n| `newsletter_subscriber:100`  | Nothing — links to customers already in the registry, falls back to guest emails       |\n\nIf you explicitly request a dependency type, your count takes precedence over the auto-calculated one.\n\n### Count-Based Seeder Files\n\nInstead of listing individual data entries, use the `count` key to generate Faker data from a seeder file:\n\n```php\n\u003c?php\n// dev/seeders/GenerateOrderSeeder.php\nreturn [\n    'type' =\u003e 'order',\n    'count' =\u003e 100,\n    'locale' =\u003e 'en_US',\n];\n```\n\nThis triggers the Faker generation pipeline (with dependency resolution) instead of the standard array-based data flow.\n\n### Commerce-quality fake data\n\nFaker's default `words()` / `sentence()` helpers produce lorem-ipsum —\nfine for `description` fields, bad for product *names*. This module\nregisters a `CommerceProvider` on every `Faker\\Generator` it hands out,\nmirroring the `commerce` module from [@faker-js/faker][fjs] (MIT).\n\nMethods available on `$faker` in any custom seeder / data generator:\n\n| Method | Example |\n|--------|---------|\n| `$faker-\u003eproductName()` | `Handcrafted Rubber Pizza` |\n| `$faker-\u003eproductAdjective()` | `Handcrafted` |\n| `$faker-\u003eproductMaterial()` | `Rubber` |\n| `$faker-\u003eproduct()` | `Pizza` |\n| `$faker-\u003eproductDepartment()` | `Electronics` |\n\nUsed internally by `ProductDataGenerator` (product names) and\n`CategoryDataGenerator` (category names). Locale is `en_US` only in v1;\nother locales silently fall back to English wordlists. See\n`src/Faker/Provider/Data/Commerce/README.md` for refresh + locale-extension\ninstructions.\n\n[fjs]: https://github.com/faker-js/faker\n\n## Product Reviews\n\nEvery seeded product automatically gets **0–10 reviews** with Faker-generated nicknames, titles, details, and a 1–5 star rating. Reviews are created against the default store (id 1) with status `Approved` so they render on the frontend immediately.\n\nNo CLI flag required — reviews are part of the product seed payload. If you want to disable reviews temporarily, set the `reviews` count range in `src/DataGenerator/ProductDataGenerator.php` (`generateReviews()` helper).\n\n## Product Types\n\nThe seeder supports all five standard Magento product types. Plain `--generate=product:N` produces a weighted mix; dotted subtypes force a specific type.\n\n### CLI\n\n```\nbin/magento db:seed --generate=product:100\nbin/magento db:seed --generate=product.configurable:20,product.bundle:10\nbin/magento db:seed --generate=product:100,product.bundle:20  # mix + force\n```\n\n### Default weights (for plain `product:N`)\n\n| Subtype       | Weight |\n|---------------|-------:|\n| simple        |    70% |\n| configurable  |    10% |\n| bundle        |    10% |\n| grouped       |     5% |\n| downloadable  |     5% |\n\nChange weights in `src/DataGenerator/ProductDataGenerator.php` — `SUBTYPE_WEIGHTS` constant.\n\n### Per-type behavior\n\n- **Simple**: as before.\n- **Configurable**: auto-creates 6 hidden simple children spanning 3 color options × 2 size options. **Requires** `color` and `size` attributes with option values on the target install — if either is missing or empty, configurable generation fails fast with a clear error. The module does not create attributes.\n- **Bundle**: creates a dynamic-price bundle with up to 3 options (select / radio / checkbox), each linking 2–3 existing simples. Falls back from registry → SEED-% products in DB → warns and skips if the simple pool is empty.\n- **Grouped**: links up to 5 existing simples via `catalog_product_link` (link type 3). Same fallback chain as bundle.\n- **Downloadable**: attaches 1–2 file-type links backed by Faker-generated `.txt` samples under `pub/media/downloadable/files/` and `pub/media/downloadable/files_sample/`.\n\n### Category distribution\n\nProducts are assigned to the category with the fewest products so far (ties go to the earliest category). As long as the run produces at least as many products as categories, every category ends up with at least one product.\n\n### Custom Data Generators\n\nAdd your own data generators via `di.xml`:\n\n```xml\n\u003ctype name=\"RunAsRoot\\Seeder\\Service\\DataGeneratorPool\"\u003e\n    \u003carguments\u003e\n        \u003cargument name=\"generators\" xsi:type=\"array\"\u003e\n            \u003citem name=\"custom_entity\" xsi:type=\"object\"\u003eVendor\\Module\\Seeder\\CustomEntityDataGenerator\u003c/item\u003e\n        \u003c/argument\u003e\n    \u003c/arguments\u003e\n\u003c/type\u003e\n```\n\nYour generator must implement `RunAsRoot\\Seeder\\Api\\DataGeneratorInterface`.\n\n## Order States\n\nOrders are generated across the real Magento lifecycle states. Plain `--generate=order:N` produces a weighted mix; dotted subtypes force a specific state.\n\n### CLI\n\n```\nbin/magento db:seed --generate=order:100\nbin/magento db:seed --generate=order.complete:50,order.canceled:10\nbin/magento db:seed --generate=order:100,order.holded:5  # mix + force\n```\n\n### Default state weights\n\n| State        | Weight |\n|--------------|-------:|\n| new          |    15% |\n| processing   |    25% |\n| complete     |    40% |\n| canceled     |    10% |\n| holded       |     5% |\n| closed       |     5% |\n\nChange weights in `src/DataGenerator/OrderDataGenerator.php` — `STATE_WEIGHTS` constant. States `pending_payment` and `payment_review` are intentionally skipped; they require payment-gateway plumbing and add little dev-data value.\n\n### Per-state behavior\n\n- **new**: default state after `CartManagementInterface::placeOrder`. No additional action.\n- **processing**: order is invoiced offline (`InvoiceService::prepareInvoice` + `register` with `CAPTURE_OFFLINE`).\n- **complete**: invoice (as above), then full shipment via `ShipmentFactory::create($order, $itemQtyMap)` + `register`.\n- **canceled**: `$order-\u003ecancel()`.\n- **holded**: `$order-\u003ehold()`.\n- **closed**: invoice, then offline refund via `CreditmemoFactory::createByOrder` + `CreditmemoManagementInterface::refund($memo, true)`.\n\nAfter each invoice-based transition, the order state is explicitly set and saved a second time because some Magento observer chains reset the state during the transaction save. Without this, invoiced orders would remain stuck at `new`.\n\n### Order items\n\nSeeded orders only use **simple** products as cart items — bundles, configurables, grouped, and downloadables require per-cart option selections that would complicate the seeder. `OrderDataGenerator` declares `product.simple` as its product dependency, so the dependency resolver always produces the right type.\n\n## Cart Rules\n\nPlain `--generate=cart_rule:N` creates N sales rules, each with one attached manual-code coupon.\n\n### CLI\n\n```\nbin/magento db:seed --generate=cart_rule:20\n```\n\n### Action mix\n\n| simple_action   | Weight | Discount amount              | Coupon prefix |\n|-----------------|-------:|-----------------------------|---------------|\n| `by_percent`    |   60%  | 5–30 (percent off)           | `SAVE`        |\n| `by_fixed`      |   30%  | 5–50 (fixed currency off)    | `DEAL`        |\n| `free_shipping` |   10%  | n/a (sets FREE_SHIPPING_ITEM)| `PROMO`       |\n\nChange weights in `src/DataGenerator/CartRuleDataGenerator.php` — `ACTION_WEIGHTS` constant.\n\n### Rule shape\n\n- Active for all websites (id 1) and all four default customer groups (NOT LOGGED IN + General + Wholesale + Retailer).\n- Empty conditions tree — applies to every cart.\n- `to_date` = today + 1 year. No `from_date` restriction.\n- Rule name: `Seed Rule — \u003ctwo Faker words\u003e` so `--fresh` can scope cleanup.\n- Coupon code: `\u003cPREFIX\u003e\u003camount\u003e-\u003c6 uppercase alnum\u003e`, e.g. `SAVE12-AB1234`. Collision retry with a random suffix, up to 3 attempts.\n\n## Wishlists\n\nPlain `--generate=wishlist:N` creates one wishlist per seeded customer, each with 1–5 randomly-picked products.\n\n### CLI\n\n```\nbin/magento db:seed --generate=wishlist:50\n```\n\nRequires at least 1 customer and 1 product in the registry (auto-generated per the dependency resolver — see the table above).\n\n### Items\n\n- `qty` = 1, `shared` = 0.\n- Items are inserted directly into `wishlist_item`, bypassing `Wishlist::addNewItem`'s stock guard. This keeps the seeder resilient to freshly-seeded products whose stock index hasn't caught up yet. Tradeoff: only simple products are exercised today — configurable / bundle wishlist items would need the full `addNewItem` option-serialization path.\n\n### Cleanup\n\n`--fresh` scopes wishlist cleanup to customers whose email matches `%@example.%` (Faker's `safeEmail()` domain). `wishlist_item` rows cascade via FK.\n\n## Newsletter Subscribers\n\nPlain `--generate=newsletter_subscriber:N` creates N subscriber rows with a roughly 50/50 mix of customer-linked and guest (unlinked) entries.\n\n### CLI\n\n```\nbin/magento db:seed --generate=newsletter_subscriber:100\n```\n\n### Behavior\n\n- If any customers are in the registry, ~half the subscribers reuse their emails + `customer_id`; the other half get guest Faker emails with `customer_id=0`.\n- Each customer is linked at most once per run — dedup is derived from the subscribers already in the registry so state never leaks between runs.\n- Status is always `Subscriber::STATUS_SUBSCRIBED`. Store id 1. `status_changed_at` set to current timestamp.\n\n### Cleanup\n\n`--fresh` scopes cleanup to `subscriber_email LIKE '%@example.%'`. If your install has real users with `@example.*` addresses, they will also match — inline comments in the handler flag this. Prefix your real users off `@example.*` if that's a concern.\n\n## Performance\n\nIterations are batched into database transactions of 50 entries each. For large runs (e.g. `--generate=product:5000`), this cuts per-iteration commit overhead roughly in half. A failing iteration rolls back only its batch's pending work and continues with the next batch.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun-as-root%2Fmagento-2-seeder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frun-as-root%2Fmagento-2-seeder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun-as-root%2Fmagento-2-seeder/lists"}