Publishing a Plugin
Vendure's plugin-based architecture means you'll be writing a lot of plugins. Some of those plugins may be useful to others, and you may want to share them with the community.
We have created Vendure Hub as a central listing for high-quality Vendure plugins.
This guide will walk you through the process of publishing a plugin to npm and submitting it to Vendure Hub.
Project setup
There are a couple of ways you can structure your plugin project:
Repo structure
We recommend that you use a "monorepo" structure to develop your plugins. This means that you have a single repository which contains all your plugins, each in its own subdirectory. This makes it easy to manage dependencies between plugins, and to share common code such as utility functions & dev tooling.
Even if you only have a single plugin at the moment, it's a good idea to set up your project in this way from the start.
To that end, we provide a monorepo plugin starter template which you can use as a starting point for your plugin development.
This starter template includes support for:
- Development & build scripts already set up
- Admin UI extensions already configured
- End-to-end testing infrastructure fully configured
- Code generation for your schema extensions
Plugin naming
We recommend that you use scoped packages for your plugins, which means
they will be named like @<scope>/<plugin-name>
. For example, if your company is called acme
, and you are publishing a plugin that
implements a loyalty points system, you could name it @acme/vendure-plugin-loyalty-points
.
Dependencies
Your plugin should not include Vendure packages as dependencies in the package.json
file. You may declare them as a peer dependencies, but this is not
a must. The same goes for any of the transitive dependencies of Vendure core such as @nestjs/graphql
, @nestjs/common
, typeorm
etc. You can assume
that these dependencies will be available in the Vendure project that uses your plugin.
As for version compatibility, you should use the compatibility property in your plugin definition to ensure that the Vendure project is using a compatible version of Vendure.
License
Your plugin must use a license that is compatible with the GPL v3 license that Vendure uses. This means your package.json
file should include "license": "GPL-3.0-or-later"
, and your repository should include a license file (usually named LICENSE.txt
) containing the
full text of the GPL v3 license.
Publishing to npm
Once your plugin is ready, you can publish it to npm. This is covered in the npm documentation on publishing packages.
Requirements for Vendure Hub
Vendure Hub is a curated list of high-quality plugins. To be accepted into Vendure Hub, we require some additional requirements be satisfied.
Changelog
Your plugin package must include a CHANGELOG.md
file which looks like this:
# Changelog
## 1.6.1 (2024-06-07)
- Fix a bug where the `foo` was not correctly bar (Fixes [#123](https://github.com/myorg/my-repo/issues/31))
## 1.6.0 (2024-03-11)
- Add a new feature to the `bar` service
- Update the `baz` service to use the new `qux` method
... etc
The exact format of the entries is up to you - you can e.g. use Keep a Changelog format, grouping by type of change, using tooling
to help generate the entries, etc. The important thing is that the CHANGELOG.md
file is present and up-to-date, and published as part of your
package by specifying it in the files
field of your package.json
file.
{
"files": [
"dist",
"README.md",
"CHANGELOG.md"
]
}
Vendure Hub will read the contents of your changelog to display the latest changes in your plugin listing.
Documentation
Good documentation is a key criteria for acceptance into Vendure Hub.
README.md
Your plugin package must include a README.md
file which contains full instructions on how to install and use your plugin. Here's a template you can use:
# Acme Loyalty Points Plugin
This plugin adds a loyalty points system to your Vendure store.
## Installation
```bash
npm install @acme/vendure-plugin-loyalty-points
```
Add the plugin to your Vendure config:
```ts
// vendure-config.ts
import { LoyaltyPointsPlugin } from '@acme/vendure-plugin-loyalty-points';
export const config = {
//...
plugins: [
LoyaltyPointsPlugin.init({
enablePartialRedemption: true,
}),
],
};
```
[If your plugin includes UI extensions]
If not already installed, install the `@vendure/ui-devkit` package:
```bash
npm install @vendure/ui-devkit
```
Then set up the compilation of the UI extensions for the Admin UI:
```ts
// vendure-config.ts
import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
import { LoyaltyPointsPlugin } from '@acme/vendure-plugin-loyalty-points';
// ...
plugins: [
AdminUiPlugin.init({
route: 'admin',
port: 3002,
app: compileUiExtensions({
outputPath: path.join(__dirname, '../admin-ui'),
extensions: [LoyaltyPointsPlugin.uiExtensions],
devMode: false,
})
}),
],
```
[/If your plugin includes UI extensions]
## Usage
Describe how to use your plugin here. Make sure to cover the key
functionality and any configuration options. Include examples
where possible.
Make sure to document any extensions made to the GraphQL APIs,
as well as how to integrate the plugin with a storefront app.
JS Docs
All publicly-exposed services, entities, strategies, interfaces etc should be documented using JSDoc comments. Not only does this improve the developer experience for your users, but it also allows Vendure Hub to auto-generate documentation pages for your plugin.
Here are some examples of well-documented plugin code (implementation details omitted for brevity):
- Plugin
- Plugin options
- Services
- Entities
- Events
- Tag the plugin with
@category Plugin
. This will be used when generating the docs pages to group plugins together. - Usually the
.init()
method is the thing that users will call to configure the plugin. Document this method with an example of how to use it. - The constructor and any lifecycle methods should be tagged with
@internal
.
/**
* Advanced search and search analytics for Vendure.
*
* @category Plugin
*/
@VendurePlugin({
imports: [PluginCommonModule],
// ...
})
export class LoyaltyPointsPlugin implements OnApplicationBootstrap {
/** @internal */
static options: LoyaltyPointsPluginInitOptions;
/**
* The static `init()` method is called with the options to
* configure the plugin.
*
* @example
* ```ts
* LoyaltyPointsPlugin.init({
* enablePartialRedemption: true
* }),
* ```
*/
static init(options: LoyaltyPointsPluginInitOptions) {
this.options = options;
return AdvancedSearchPlugin;
}
/**
* The static `uiExtensions` property is used to provide the
* necessary UI extensions to the Admin UI
* in order to display the loyalty points admin features.
* This property is used in the `AdminUiPlugin` initialization.
*
* @example
* ```ts
* import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
* import { AdvancedSearchPlugin } from '@acme/vendure-plugin-loyalty-points';
*
* // ...
* plugins: [
* AdminUiPlugin.init({
* route: 'admin',
* port: 3002,
* app: compileUiExtensions({
* outputPath: path.join(__dirname, '../admin-ui'),
* extensions: [LoyaltyPointsPlugin.uiExtensions],
* devMode: false,
* })
* }),
* ],
* ```
*/
static uiExtensions = advancedSearchPluginUi;
/** @internal */
constructor(/* ... */) {}
/** @internal */
async onApplicationBootstrap() {
// Logic to set up event subscribers etc.
}
}
- Tag the options interface with
@category Plugin
. - Document any default values for optional properties.
/**
* Configuration options for the LoyaltyPointsPlugin.
*
* @category Plugin
*/
export interface LoyaltyPointsPluginInitOptions {
/**
* Whether to allow partial redemption of points.
*
* @default true
*/
enablePartialRedemption?: boolean;
}
- Only services that are exported in the plugin's
exports
array need to be documented. Internal services can be left undocumented. - Tag services with
@category Services
. This will be used when generating the docs pages to group services together. - By default all non-private methods are included in the docs. If you want to exclude a method, tag it with
@internal
.
/**
* The LoyaltyPointsService provides methods for managing a
* customer's loyalty points balance.
*
* @category Services
*/
@Injectable()
export class LoyaltyPointsService {
/** @internal */
constructor(private connection: TransactionalConnection) {}
/**
* Adds the given number of points to the customer's balance.
*/
addPoints(ctx: RequestContext, customerId: ID, points: number): Promise<LoyaltyPointsTransaction> {
// implementation...
}
/**
* Deducts the given number of points from the customer's balance.
*/
deductPoints(customerId: ID, points: number): Promise<LoyaltyPointsTransaction> {
// implementation...
}
}
- Tag entities with
@category Entities
. This will be used when generating the docs pages to group entities together.
/**
* Represents a transaction of loyalty points,
* when points are added or deducted.
*
* @category Entities
*/
@Entity()
export class LoyaltyPointsTransaction extends VendureEntity {
/**
* The number of points added or deducted.
* A negative value indicates points deducted.
*/
@Column()
points: number;
/**
* The Customer to whom the points were added or deducted.
*/
@ManyToOne(type => Customer)
customer: Customer;
/**
* The reason for the points transaction.
*/
@Column()
reason: string;
}
- Tag events with
@category Events
. This will be used when generating the docs pages to group events together.
/**
* This event is fired whenever a LoyaltyPointsTransaction is created.
*
* @category Events
*/
export class LoyaltyPointsTransactionEvent extends VendureEvent {
constructor(public ctx: RequestContext, public transaction: LoyaltyPointsTransaction) {
super();
}
}
Tests
Testing is an important part of ensuring the quality of your plugin, as well as preventing regressions when you make changes.
For plugins of any complexity, you should aim to have a suite of end-to-end tests as covered in the testing docs.
In future we may use the test results to help determine the quality of a plugin.
Submitting to Vendure Hub
Once your plugin is published to npm and satisfies the requirements above, you can submit it to Vendure Hub via our contact form, making sure to include a link to the npm package and the GitHub repository.
Publishing a paid plugin
Vendure Hub supports the listing of paid plugins. If you would like to sell plugins through Vendure Hub, please contact us and provide details about the plugins you would like to sell.