vendure-developing

Develop Vendure e-commerce plugins, extend GraphQL APIs, create Admin UI components, and define database entities. Use vendure-expert agent for comprehensive guidance across all Vendure development domains.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "vendure-developing" with this command: npx skills add meriley/claude-code-skills/meriley-claude-code-skills-vendure-developing

Vendure Development

Purpose

Entry point for all Vendure development tasks. Provides quick reference and guides to the vendure-expert agent for coordinated multi-domain guidance.

When NOT to Use

  • Non-Vendure e-commerce platforms (Shopify, Magento, etc.)
  • Basic TypeScript/NestJS without Vendure context
  • Frontend-only React without Vendure Admin UI

Quick Start: Use the Agent

For comprehensive Vendure guidance, use the vendure-expert agent:

Task(subagent_type: "vendure-expert", prompt: "Your Vendure task here")

The agent coordinates 9 specialized skills:

  • Plugin development (writing + reviewing)
  • GraphQL API (writing + reviewing)
  • Entity/Database (writing + reviewing)
  • Admin UI (writing + reviewing)
  • Delivery/shipping features (specialized)

Vendure Architecture Quick Reference

6 Core Domains

DomainKey Concepts
Plugins@VendurePlugin, NestJS DI, lifecycle hooks
GraphQLDual APIs (Shop/Admin), RequestContext, gql template
EntitiesVendureEntity, TypeORM, migrations, custom fields
Admin UIReact/Angular, UI DevKit, lazy loading
StrategiesInjectableStrategy, custom logic
EventsEventBus, transaction-safe subscriptions

Plugin Scaffold

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

@VendurePlugin({
  imports: [PluginCommonModule],
  providers: [MyService],
  entities: [MyEntity],
  adminApiExtensions: {
    schema: gql`...`,
    resolvers: [MyResolver],
  },
})
export class MyPlugin {
  static init(options: MyPluginOptions) {
    this.options = options;
    return MyPlugin;
  }
}

GraphQL Resolver Pattern

import { Ctx, RequestContext, Query, Resolver } from "@vendure/core";
import { Allow, Permission } from "@vendure/core";

@Resolver()
export class MyResolver {
  constructor(private myService: MyService) {}

  @Query()
  @Allow(Permission.ReadSettings)
  async myQuery(@Ctx() ctx: RequestContext): Promise<MyType[]> {
    return this.myService.findAll(ctx);
  }
}

Entity Pattern

import { VendureEntity, DeepPartial } from "@vendure/core";
import { Entity, Column, ManyToOne } from "typeorm";

@Entity()
export class MyEntity extends VendureEntity {
  constructor(input?: DeepPartial<MyEntity>) {
    super(input);
  }

  @Column()
  name: string;

  @ManyToOne(() => OtherEntity)
  relation: OtherEntity;
}

Admin UI Extension

// providers.ts
import { addNavMenuSection } from "@vendure/admin-ui/react";

export default [
  addNavMenuSection(
    {
      id: "my-section",
      label: "My Feature",
      items: [
        {
          id: "my-page",
          label: "My Page",
          routerLink: ["/extensions/my-feature"],
          icon: "cog",
        },
      ],
    },
    "settings",
  ),
];

FORBIDDEN Patterns

Plugin Development

  • Missing @VendurePlugin decorator
  • Not using NestJS DI (@Injectable)
  • Hardcoded values instead of plugin config
  • Direct database access bypassing services

GraphQL

  • Missing @Ctx() RequestContext parameter
  • Bypassing @Allow() permission decorator
  • Mixing Shop/Admin schema types
  • Not using gql template literal

Entities

  • Not extending VendureEntity
  • Missing @Entity() decorator
  • No migration file created
  • Using any type

Admin UI

  • Not lazy loading routes
  • Hardcoded strings (not using i18n)
  • Missing loading/error states
  • Not handling permissions

REQUIRED Patterns

RequestContext Threading

// ALWAYS pass ctx through service calls
async myResolver(@Ctx() ctx: RequestContext) {
  return this.service.findAll(ctx);  // Pass ctx!
}

Permission Decorators

@Query()
@Allow(Permission.ReadCatalog)  // ALWAYS specify permissions
async products(@Ctx() ctx: RequestContext) { }

Entity Input Types

// Use DeepPartial for constructor input
constructor(input?: DeepPartial<MyEntity>) {
  super(input);
}

InputMaybe Handling

// Check BOTH undefined AND null for GraphQL inputs
if (input.field !== undefined && input.field !== null) {
  entity.field = input.field;
}

Domain Skill Decision Tree

Task Type
    │
    ├─> Creating/modifying plugin structure
    │   └─> vendure-plugin-writing
    │
    ├─> Reviewing plugin code
    │   └─> vendure-plugin-reviewing
    │
    ├─> Extending GraphQL schema or resolvers
    │   └─> vendure-graphql-writing
    │
    ├─> Reviewing GraphQL code
    │   └─> vendure-graphql-reviewing
    │
    ├─> Creating/modifying entities
    │   └─> vendure-entity-writing
    │
    ├─> Reviewing entity definitions
    │   └─> vendure-entity-reviewing
    │
    ├─> Building Admin UI components
    │   └─> vendure-admin-ui-writing
    │
    ├─> Reviewing Admin UI code
    │   └─> vendure-admin-ui-reviewing
    │
    └─> Delivery/shipping features
        └─> vendure-delivery-plugin

Common Patterns

Dual API Separation

// Admin API - full access
export const graphqlAdminSchema = gql`
  extend type Query {
    myAdminQuery: [MyType!]!
  }
  extend type Mutation {
    updateMyType(input: UpdateInput!): MyType!
  }
`;

// Shop API - customer-facing, read-only or limited
export const graphqlShopSchema = gql`
  extend type Query {
    myPublicQuery: [MyType!]!
  }
`;

Service Pattern with RequestContext

@Injectable()
export class MyService {
  constructor(private connection: TransactionalConnection) {}

  async findAll(ctx: RequestContext): Promise<MyEntity[]> {
    return this.connection.getRepository(ctx, MyEntity).find();
  }

  async create(ctx: RequestContext, input: CreateInput): Promise<MyEntity> {
    const entity = new MyEntity(input);
    return this.connection.getRepository(ctx, MyEntity).save(entity);
  }
}

Transaction Decorator

@Mutation()
@Transaction()  // Wrap in database transaction
@Allow(Permission.UpdateSettings)
async updateMyEntity(
  @Ctx() ctx: RequestContext,
  @Args() { input }: { input: UpdateInput }
): Promise<MyEntity> {
  return this.service.update(ctx, input);
}

Examples

Example 1: Create a Simple Plugin

Task: Create a plugin that tracks product views.

Approach:

  1. Use vendure-plugin-writing for plugin scaffold
  2. Use vendure-entity-writing for ProductView entity
  3. Use vendure-graphql-writing for query extension

Result:

@VendurePlugin({
  imports: [PluginCommonModule],
  entities: [ProductViewEntity],
  providers: [ProductViewService],
  shopApiExtensions: {
    schema: gql`
      extend type Query {
        productViews(productId: ID!): Int!
      }
    `,
    resolvers: [ProductViewResolver],
  },
})
export class ProductViewsPlugin {}

Example 2: Add Admin UI Page

Task: Add a settings page to manage plugin configuration.

Approach:

  1. Use vendure-admin-ui-writing for React components
  2. Register route and navigation item
  3. Use DataService for API calls

Result:

// pages/SettingsPage.tsx
export function SettingsPage() {
  const dataService = useInjector(DataService);
  const [settings, setSettings] = useState<Settings>();

  useEffect(() => {
    dataService
      .query(GetSettingsDocument)
      .stream$.subscribe((result) => setSettings(result.mySettings));
  }, []);

  return <SettingsForm settings={settings} />;
}

Example 3: Review Plugin Code

Task: Audit a plugin for security and best practices.

Approach:

  1. Use vendure-plugin-reviewing for plugin structure
  2. Use vendure-graphql-reviewing for resolver patterns
  3. Use vendure-entity-reviewing for database patterns

Checks:

  • RequestContext passed through all service calls
  • Permissions declared on all resolvers
  • No direct database queries (use TransactionalConnection)
  • Proper error handling

Troubleshooting

ProblemCauseSolution
Entity not foundNot in plugin entities arrayAdd to @VendurePlugin({ entities: [] })
Resolver not calledNot in resolvers arrayAdd to apiExtensions.resolvers
Permission deniedMissing @Allow decoratorAdd @Allow(Permission.X)
TypeScript errorsWrong import pathImport from @vendure/core
Admin UI not loadingNot lazy loadedUse React.lazy() for routes

External Documentation

Context7 (Recommended)

mcp__context7__get-library-docs
context7CompatibleLibraryID: "/vendure-ecommerce/vendure"
topic: "plugins" or "entities" or "graphql" etc.

Official Docs


Related Agent

For comprehensive multi-domain Vendure guidance, use the vendure-expert agent.

The agent coordinates all 9 Vendure skills and provides:

  • Full plugin lifecycle guidance
  • Security and quality audits
  • Best practices enforcement
  • Domain-specific patterns

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

obs-cpp-qt-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

vendure-admin-ui-writing

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

vendure-admin-ui-reviewing

No summary provided by upstream source.

Repository SourceNeeds Review