🎯

vendure-plugin-writing

🎯Skill

from meriley/claude-code-skills

VibeIndex|
What it does

Guides developers in creating robust, configurable Vendure plugins using TypeScript, decorators, and dependency injection best practices.

πŸ“¦

Part of

meriley/claude-code-skills(18 items)

vendure-plugin-writing

Installation

git cloneClone repository
git clone https://gitea.cmtriley.com/mriley/claude-config.git .claude
npm installInstall npm package
npm install -g @anthropic-ai/claude-code
pip installInstall Python package
pip install uv
πŸ“– Extracted from docs: meriley/claude-code-skills
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Create production-ready Vendure plugins with @VendurePlugin decorator, NestJS dependency injection, lifecycle hooks, and configuration patterns. Use when developing new Vendure plugins or extending existing ones.

Overview

# Vendure Plugin Writing

Purpose

Guide creation of Vendure plugins following official best practices and production patterns.

When NOT to Use

  • Reviewing existing plugins (use vendure-plugin-reviewing)
  • GraphQL-specific work (use vendure-graphql-writing)
  • Entity definition only (use vendure-entity-writing)
  • Admin UI only (use vendure-admin-ui-writing)

---

FORBIDDEN Patterns

  • Missing @VendurePlugin decorator
  • Not importing PluginCommonModule
  • Not using @Injectable on services
  • Hardcoded values instead of plugin configuration
  • Direct database access bypassing services
  • Missing async/await on lifecycle hooks
  • Constructor injection in strategies (use init() method)
  • Skipping default values for optional config

---

REQUIRED Patterns

  • @VendurePlugin() decorator with proper metadata
  • PluginCommonModule in imports array
  • Configuration interface with sensible defaults
  • Static init() method for plugin configuration
  • @Injectable() decorator on all services
  • Proper DI via constructor injection in services
  • Lifecycle hooks when needed (onApplicationBootstrap)

---

Workflow

Step 1: Define Plugin Configuration Interface

```typescript

// my-plugin.types.ts

export interface MyPluginOptions {

enabled?: boolean;

apiKey?: string;

maxRetries?: number;

}

// Default configuration

export const defaultMyPluginOptions: Required = {

enabled: true,

apiKey: "",

maxRetries: 3,

};

```

Step 2: Create the Plugin Class

```typescript

// my-plugin.plugin.ts

import { PluginCommonModule, VendurePlugin } from "@vendure/core";

import { MyService } from "./my.service";

import { MyEntity } from "./my.entity";

import { MyResolver } from "./my.resolver";

import { MyPluginOptions, defaultMyPluginOptions } from "./my-plugin.types";

@VendurePlugin({

imports: [PluginCommonModule],

providers: [MyService],

entities: [MyEntity],

adminApiExtensions: {

schema: graphqlAdminSchema,

resolvers: [MyResolver],

},

})

export class MyPlugin {

static options: Required;

static init(options: MyPluginOptions = {}): typeof MyPlugin {

this.options = {

...defaultMyPluginOptions,

...options,

};

return MyPlugin;

}

}

```

Step 3: Create Injectable Service

```typescript

// my.service.ts

import { Injectable } from "@nestjs/common";

import { TransactionalConnection, RequestContext } from "@vendure/core";

import { MyEntity } from "./my.entity";

@Injectable()

export class MyService {

constructor(private connection: TransactionalConnection) {}

async findAll(ctx: RequestContext): Promise {

return this.connection.getRepository(ctx, MyEntity).find();

}

async create(ctx: RequestContext, input: CreateInput): Promise {

const entity = new MyEntity(input);

return this.connection.getRepository(ctx, MyEntity).save(entity);

}

}

```

Step 4: Add Lifecycle Hooks (If Needed)

```typescript

import { OnApplicationBootstrap, OnApplicationShutdown } from "@nestjs/common";

import { Injector } from "@vendure/core";

@VendurePlugin({

// ... metadata

})

export class MyPlugin implements OnApplicationBootstrap, OnApplicationShutdown {

static options: Required;

constructor(private injector: Injector) {}

async onApplicationBootstrap(): Promise {

// Initialize strategies, start background tasks, etc.

if (MyPlugin.options.enabled) {

const service = this.injector.get(MyService);

await service.initialize();

}

}

async onApplicationShutdown(): Promise {

// Cleanup resources

}

}

```

Step 5: Register Plugin in vendure-config.ts

```typescript

import { MyPlugin } from "./plugins/my-plugin/my-plugin.plugin";

export const config: VendureConfig = {

plugins: [

MyPlugin.init({

enabled: true,

apiKey: process.env.MY_API_KEY,

maxRetries: 5,

}),

],

};

```

---

Plugin Structure Template

```

my-plugin/

β”œβ”€β”€ my-plugin.plugin.ts # Main plugin file

β”œβ”€β”€ my-plugin.types.ts # Configuration types

β”œβ”€β”€ my.service.ts # Business logic

β”œβ”€β”€ my.entity.ts # Database entity

β”œβ”€β”€ my.resolver.ts # GraphQL resolver

β”œβ”€β”€ schema.ts # GraphQL schema extensions

β”œβ”€β”€ strategies/ # Custom strategies

β”‚ └── my.strategy.ts

β”œβ”€β”€ ui/ # Admin UI extensions

β”‚ └── providers.ts

└── index.ts # Public exports

```

---

Examples

Example 1: Simple Analytics Plugin

```typescript

import {

PluginCommonModule,

VendurePlugin,

EventBus,

ProductEvent,

} from "@vendure/core";

import { OnApplicationBootstrap } from "@nestjs/common";

interface AnalyticsOptions {

trackingId: string;

}

@VendurePlugin({

imports: [PluginCommonModule],

})

export class AnalyticsPlugin implements OnApplicationBootstrap {

static options: AnalyticsOptions;

constructor(private eventBus: EventBus) {}

static init(options: AnalyticsOptions): typeof AnalyticsPlugin {

this.options = options;

return AnalyticsPlugin;

}

async onApplicationBootstrap(): Promise {

this.eventBus.ofType(ProductEvent).subscribe((event) => {

console.log(Product ${event.type}: ${event.entity.name});

// Send to analytics service

});

}

}

```

Example 2: Plugin with Custom Strategy

```typescript

import { PluginCommonModule, VendurePlugin, Injector } from "@vendure/core";

import { OnApplicationBootstrap, OnApplicationShutdown } from "@nestjs/common";

import { MyStrategy, DefaultMyStrategy } from "./strategies/my.strategy";

interface PluginOptions {

strategy?: MyStrategy;

}

@VendurePlugin({

imports: [PluginCommonModule],

})

export class StrategyPlugin

implements OnApplicationBootstrap, OnApplicationShutdown

{

static options: Required;

constructor(private injector: Injector) {}

static init(options: PluginOptions = {}): typeof StrategyPlugin {

this.options = {

strategy: new DefaultMyStrategy(),

...options,

};

return StrategyPlugin;

}

async onApplicationBootstrap(): Promise {

// Initialize strategy with injector for DI

if (typeof StrategyPlugin.options.strategy.init === "function") {

await StrategyPlugin.options.strategy.init(this.injector);

}

}

async onApplicationShutdown(): Promise {

if (typeof StrategyPlugin.options.strategy.destroy === "function") {

await StrategyPlugin.options.strategy.destroy();

}

}

}

```

Example 3: Plugin with Admin/Shop API Extensions

```typescript

import { PluginCommonModule, VendurePlugin } from "@vendure/core";

import { gql } from "graphql-tag";

import { MyService } from "./my.service";

import { MyAdminResolver, MyShopResolver } from "./my.resolver";

const graphqlAdminSchema = gql`

extend type Query {

myAdminData: [MyType!]!

}

extend type Mutation {

updateMyData(input: UpdateInput!): MyType!

}

`;

const graphqlShopSchema = gql`

extend type Query {

myPublicData: [MyType!]!

}

`;

@VendurePlugin({

imports: [PluginCommonModule],

providers: [MyService],

adminApiExtensions: {

schema: graphqlAdminSchema,

resolvers: [MyAdminResolver],

},

shopApiExtensions: {

schema: graphqlShopSchema,

resolvers: [MyShopResolver],

},

})

export class ApiPlugin {}

```

---

Common Patterns

Accessing Plugin Options in Service

```typescript

@Injectable()

export class MyService {

get options() {

return MyPlugin.options;

}

async doSomething() {

if (this.options.enabled) {

// Use this.options.apiKey

}

}

}

```

Custom Fields Configuration

```typescript

@VendurePlugin({

imports: [PluginCommonModule],

configuration: (config) => {

config.customFields.Product.push({

name: "myCustomField",

type: "string",

label: [{ languageCode: "en", value: "My Custom Field" }],

});

return config;

},

})

export class CustomFieldsPlugin {}

```

Plugin Dependencies

```typescript

// If plugin depends on another plugin's services

@VendurePlugin({

imports: [PluginCommonModule, OtherPlugin],

providers: [MyService],

})

export class DependentPlugin {}

```

---

Troubleshooting

| Problem | Cause | Solution |

| ------------------------ | ---------------------- | ----------------------------------------- |

| Service not found | Not in providers array | Add to @VendurePlugin({ providers: [] }) |

| Entity not created | Not in entities array | Add to @VendurePlugin({ entities: [] }) |

| Options undefined | init() not called | Call MyPlugin.init() in vendure-config.ts |

| Circular dependency | Improper imports | Use forwardRef() or restructure modules |

| Strategy not initialized | Missing init() call | Implement OnApplicationBootstrap |

---

Related Skills

  • vendure-plugin-reviewing - Audit plugin for violations
  • vendure-graphql-writing - GraphQL schema and resolvers
  • vendure-entity-writing - Database entities
  • vendure-admin-ui-writing - Admin UI extensions

---

External Documentation

Context7

```

mcp__context7__get-library-docs

context7CompatibleLibraryID: "/vendure-ecommerce/vendure"

topic: "plugins"

```

Official Docs

  • https://docs.vendure.io/guides/developer-guide/plugins/
  • https://docs.vendure.io/guides/developer-guide/plugin-lifecycle/

More from this repository10

🎯
vendure-admin-ui-writing🎯Skill

vendure-admin-ui-writing skill from meriley/claude-code-skills

🎯
vendure-admin-ui-reviewing🎯Skill

Reviews and analyzes the Vendure admin user interface codebase, providing insights into code quality, potential improvements, and identifying potential UI/UX issues or technical debt in the admin p...

🎯
safe-commit🎯Skill

Enforces comprehensive security, quality, and approval checks for safe and consistent code commits, preventing manual git operations.

🎯
skill-writing🎯Skill

Guides developers in creating high-quality Claude Code skills by providing a structured workflow for identifying needs, designing evaluations, and documenting skills effectively.

🎯
check-history🎯Skill

Automatically reviews git repository context by running parallel git commands to understand current state, recent changes, and related work before starting any task.

🎯
vendure-developing🎯Skill

Develops comprehensive Vendure e-commerce solutions by creating plugins, extending GraphQL APIs, building Admin UI components, and defining custom database entities.

🎯
run-tests🎯Skill

Runs automated tests for code projects, executing test suites and reporting results across different programming environments.

🎯
security-scan🎯Skill

I apologize, but I cannot generate a description without seeing the actual context or details about the "security-scan" skill. Could you provide more information about what the skill does, its purp...

🎯
safe-destroy🎯Skill

Enforces strict safety protocols by requiring explicit confirmation, listing affected files, and preventing accidental data loss during destructive git or file operations.

🎯
vendure-plugin-reviewing🎯Skill

Extends Vendure e-commerce platform with advanced product review capabilities, enabling custom review management and integration.