---
title: "Writing a Vendure Plugin"
weight: 0
---

# Writing a Vendure Plugin

A Vendure plugin is a class which implements the [`VendurePlugin` interface]({{< relref "vendure-plugin" >}}). This interface allows the plugin to:

1. Modify the [VendureConfig]({{< relref "vendure-config" >}}) object.
2. Extend the GraphQL API, including modifying existing types and adding completely new queries and mutations.
3. Define new database entities and interact directly with the database.
4. Run code before the server bootstraps, such as starting webservers.

## Example: RandomCatPlugin

Let's learn about these capabilities by writing a plugin which defines a new database entity and GraphQL mutation.

This plugin will add a new mutation, `addRandomCat`, to the GraphQL API which allows us to conveniently link a random cat image from [http://random.cat](http://random.cat) to any product in out catalog.

### Step 1: Define a new custom field

We need a place to store the url of the cat image, so we will add a custom field to the Product entity. This is done by modifying the VendureConfig object in the the plugin's [`configure` method]({{< relref "vendure-plugin" >}}#configure):

```ts 
import { VendurePlugin } from '@vendure/core';

export class RandomCatPlugin implements VendurePlugin {
    configure(config) {
        config.customFields.Product.push({
            type: 'string',
            name: 'catImageUrl',
        });
        return config;
    }
}
```

### Step 2: Create a service to fetch the data

Now we will create a service which is responsible for making the HTTP call to the random.cat API and returning the URL of a random cat image:

```ts 
import http from 'http';
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatFetcher {
    /** Fetch a random cat image url from random.cat */
    fetchCat(): Promise<string> {
        return new Promise((resolve) => {
            http.get('http://aws.random.cat/meow', (resp) => {
                let data = '';
                resp.on('data', chunk => data += chunk);
                resp.on('end', () => resolve(JSON.parse(data).file));
            });
        });
    }
}
```

{{% alert %}}
The `@Injectable()` decorator is part of the underlying [Nest framework](https://nestjs.com/), and allows us to make use of Nest's powerful dependency injection features. In this case, we'll be able to inject the `CatFetcher` service into the resolver which we will soon create.
{{% /alert %}}


{{% alert "warning" %}}
To use decorators with TypeScript, you must set the "emitDecoratorMetadata" and "experimentalDecorators" compiler options to `true` in your tsconfig.json file.
{{% /alert %}}

### Step 3: Define the new mutation

Next we will define how the GraphQL API should be extended:

```ts 
import gql from 'graphql-tag';

export class RandomCatPlugin implements VendurePlugin {

    private schemaExtension = gql`
        extend type Mutation {
            addRandomCat(id: ID!): Product!
        }
    `;

    configure(config) {
        // as above
    }
}
```

We will use this private `schemaExtension` variable in a later step.

### Step 4: Create a resolver

Now that we've defined the new mutation, we'll need a resolver function to handle it. To do this, we'll create a new resolver class, following the [Nest GraphQL resolver architecture](https://docs.nestjs.com/graphql/resolvers-map). In short, this will be a class which has the `@Resolver()` decorator and features a method to handle our new mutation.

```ts 
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { Ctx, Allow, ProductService, RequestContext } from '@vendure/core';

@Resolver()
export class RandomCatResolver {

    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}

    @Mutation()
    @Allow(Permission.UpdateCatalog)
    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
        const catImageUrl = await this.catFetcher.fetchCat();
        return this.productService.update(ctx, {
            id: args.id,
            customFields: { catImageUrl },
        });
    }
}
```

Some explanations of this code are in order:

* The `@Resolver()` decorator tells Nest that this class contains GraphQL resolvers.
* We are able to use Nest's dependency injection to inject an instance of our `CatFetcher` class into the constructor of the resolver. We are also injecting an instance of the built-in `ProductService` class, which is responsible for operations on Products.
* We use the `@Mutation()` decorator to mark this method as a resolver for a mutation with the corresponding name.
* The `@Allow()` decorator enables us to define permissions restrictions on the mutation. Only those users whose permissions include `UpdateCatalog` may perform this operation. For a full list of available permissions, see the [Permission enum]({{< relref "../graphql-api/admin/enums" >}}#permission).
* The `@Ctx()` decorator injects the current `RequestContext` into the resolver. This provides information about the current request such as the current Session, User and Channel. It is required by most of the internal service methods.
* The `@Args()` decorator injects the arguments passed to the mutation as an object.

### Step 5: Export any providers used in the resolver

In order that out resolver is able to use Nest's dependency injection to inject and instance of `CatFetcher`, we must export it via the [`defineProviders` method](({{< relref "vendure-plugin" >}}#defineproviders)) in our plugin:

```ts 
export class RandomCatPlugin implements VendurePlugin {

    // ...

    defineProviders() {
        return [CatFetcher];
    }
}
```

### Step 6: Extend the GraphQL API

Now that we've defined the new mutation and we have a resolver capable of handling it, we just need to tell Vendure to extend the API. This is done with the [`extendAdminAPI` method]({{< relref "vendure-plugin" >}}#extendadminapi). If we wanted to extend the Shop API, we'd use the [`extendShopAPI` method]({{< relref "vendure-plugin" >}}#extendshopapi) method instead.

```ts 
export class RandomCatPlugin implements VendurePlugin {

    // ...
    
    extendAdminAPI() {
        return {
            schema: this.schemaExtension,
            resolvers: [RandomCatResolver],
        };
    }
}
```

### Step 7: Add the plugin to the Vendure config

Finally we need to add an instance of our plugin to the config object with which we bootstrap out Vendure server:

```ts 
import { bootstrap } from '@vendure/core';

bootstrap({
    // .. config options
    plugins: [
        new RandomCatPlugin(),
    ],
});
```

### Step 8: Test the plugin

Once we have started the Vendure server with the new config, we should be able to send the following GraphQL query to the Admin API:

```GraphQL
mutation {
  addRandomCat(id: "1") {
    id
    name
    customFields {
      catImageUrl
    }
  }
}
```

which should yield the following response:

```JSON 
{
  "data": {
    "addRandomCat": {
      "id": "1",
      "name": "Spiky Cactus",
      "customFields": {
        "catImageUrl": "https://purr.objects-us-east-1.dream.io/i/OoNx6.jpg"
      }
    }
  }
}
```

### Full example plugin

```ts
import { Injectable } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import gql from 'graphql-tag';
import http from 'http';
import { Allow, Ctx, Permission, ProductService, RequestContext, VendureConfig, VendurePlugin } from '@vendure/core';

export class RandomCatPlugin implements VendurePlugin {

    private schemaExtension = gql`
        extend type Mutation {
            addRandomCat(id: ID!): Product!
        }
    `;

    configure(config: Required<VendureConfig>) {
        config.customFields.Product.push({
            type: 'string',
            name: 'catImageUrl',
        });
        return config;
    }

    defineProviders() {
        return [CatFetcher];
    }

    extendAdminAPI() {
        return {
            schema: this.schemaExtension,
            resolvers: [RandomCatResolver],
        };
    }
}

@Injectable()
export class CatFetcher {
    /** Fetch a random cat image url from random.cat */
    fetchCat(): Promise<string> {
        return new Promise((resolve) => {
            http.get('http://aws.random.cat/meow', (resp) => {
                let data = '';
                resp.on('data', chunk => data += chunk);
                resp.on('end', () => resolve(JSON.parse(data).file));
            });
        });
    }
}

@Resolver()
export class RandomCatResolver {
    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}

    @Mutation()
    @Allow(Permission.UpdateCatalog)
    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
        const catImageUrl = await this.catFetcher.fetchCat();
        return this.productService.update(ctx, {
            id: args.id,
            customFields: { catImageUrl },
        });
    }
}
```
