Contributing
StoreSeeder is GPL-2.0 and developed in the open. Bug reports, fixes, new platform drivers and new recipes are all welcome.
This page orients you. The process — branching, commit format, what a pull request needs — lives in CONTRIBUTING.md, which is the canonical version; nothing here restates it, because two copies of a process is how they start disagreeing.
Setting up
Section titled “Setting up”Requirements: PHP 7.4+, Composer, Node 16+, and a WordPress install with one supported store plugin active — WooCommerce or Fluent Cart.
git clone https://github.com/mralaminahamed/storeseeder.gitcd storeseedercomposer installyarn installyarn buildThere is deliberately no Requires Plugins header. It would make WordPress refuse activation without
one named plugin, which would put every other platform out of reach — so StoreSeeder activates either
way and reports what is missing. That is what lets a driver be developed against a store plugin you
have not installed yet.
The quality gates
Section titled “The quality gates”Everything a pull request has to pass, and all of it runnable locally:
| Command | What it checks |
|---|---|
composer phpcs |
WordPress Coding Standards over includes/ |
composer phpstan |
Static analysis at level 7 |
composer test |
PHPUnit — needs a test database, see CONTRIBUTING.md |
yarn lint:js |
ESLint over src/ |
npx tsc --noEmit |
The only type-check the tests get; Jest transpiles through Babel |
yarn test:unit |
Jest and React Testing Library, colocated beside their source |
yarn build |
The production bundle |
Two conventions that surprise people: PHP indents with tabs (per WPCS, and the .editorconfig is
wrong about it), and PHP methods and variables are snake_case. TypeScript indents with two
spaces.
Adding a platform
Section titled “Adding a platform”The whole surface is one filter. Append an object implementing Platform_Interface — extending
Platform_Driver is shortest — and the generators, the REST API, WP-CLI and the admin pick it up
without further changes.
add_filter( 'storeseeder_platforms', function ( array $platforms ): array { $platforms[] = new My_Store_Driver();
return $platforms;} );A driver answers four questions: what it is called, whether it is active, which resources it supports, and which writer handles each one. Two rules matter more than the rest:
- A refusal has to say which kind it is.
Capability::unsupported( $reason )is a concept the platform does not have;Capability::missing_extension( $slug, $label )is one a plugin would enable. They read differently to a user — one is a dead end, the other is a link — and reporting the first where the second is true sends somebody looking for a setting that does not exist. - Money arrives as an integer in the currency’s minor unit. Never a float. Divide in your writer if your platform stores decimals.
Full detail in Extension points and Architecture.
Adding a generator
Section titled “Adding a generator”Six pieces, and all six are needed or the resource is half-registered:
includes/Generation/Generators/— extendsGeneration\Generator, implementsbuild_entity()includes/Platforms/Drivers/<Platform>/Writers/— extendsPlatforms\Writer- That driver’s
writer_classes()andcapabilities() includes/Platforms/Resource.php— the canonical nameincludes/Rest/Controllers/— extendsRest\Controller, listed inRest\Registrysrc/lib/generators.ts— admin registration, parameter schema, and theresourcekey
The one rule worth internalising: a generator may not name a platform. No models, no table names, no platform-specific status strings, no database reads. That constraint is what lets one generator feed every platform and lets a fixed seed produce the same data everywhere.
The other: a declared parameter must change the output. If it appears in the admin, the REST schema
or an MCP tool’s inputs, it has to reach build_entity() or a writer. Where one cannot be honoured it
is removed rather than left looking functional — a control that moves, saves and changes nothing is
worse than an absent one.
Adding a recipe
Section titled “Adding a recipe”Recipes live in their own repository, so a new shop type or a new locale for an existing one needs no plugin release. A recipe is a vocabulary — product names, a category tree, brand names, a price band, variation axes — never a dump of records.
You can also register one from your own plugin with no download at all:
add_filter( 'storeseeder_recipes', function ( array $manifests ): array { $manifests[] = json_decode( file_get_contents( __DIR__ . '/my-recipe/recipe.json' ), true );
return $manifests;} );
add_filter( 'storeseeder_recipe_directories', function ( array $dirs ): array { $dirs['my-recipe'] = __DIR__ . '/my-recipe';
return $dirs;} );See Recipes for the manifest format, and the caution there about
params keys — all three shipped recipes once declared a coupon parameter the generator does not read,
ran to completion, and reported success while ignoring it.
Reporting a bug
Section titled “Reporting a bug”Support & troubleshooting lists what to include. The short version: the seed, the locale, the count, the target platform and its version, and which store plugins are active. With a seed, whoever reads the report gets the data you got.
For a vulnerability, use a private advisory rather than a public issue.