Mailchimp Developer LogoMailchimp Developer Wordmark

Build an API Plugin

At a glance

Mailchimp Open Commerce has a plugin system that allows you to extend or replace any of the features offered by the core platform. You can add plugins written by the Open Commerce community, or you can create your own. The plugins that you register will determine the exact functionality of your API.

You can use plugins to:

  • Add new types of information to products

  • Accept payments or ship orders with your preferred provider

  • Validate customers’ addresses when they check out

For the purposes of this guide, we manage the Open Commerce store for Leader of the Pack, an outdoor supplier that sells backpacks, among other things. We currently list fairly standard product dimensions—length, width, height—but recently, our customers have been asking just how much stuff they can fit in our backpacks. To answer their questions, we’ve decided to add a volume attribute to those products in our store.

In this guide, we’ll create a new plugin from the template provided by Open Commerce and register it with the platform running in development mode. Then we’ll set up the volume field and add it to the schemas for our products. Finally, we’ll extend the GraphQL API so our new field is available across our store.

Clone the example plugin template

We’ll be creating a plugin that extends our products schema to include a volume attribute for our backpacks. To build our custom plugin, we’ll first create our own repo from the Example Plugin template on GitHub and then clone the new plugin to our working directory. (Cloning to reaction-development-platform/api-plugins is recommended but not required.)

Next, we’ll edit the package.json and src/index.js files to replace the template defaults with information about our volume attribute plugin; the information in these files will be used when linking, installing, and registering the plugin. We’ll name our plugin Product Volume, with an npm name of @leaderpack/api-plugin-products-volume.

Note: To keep things organized, it’s a good idea to name plugins after their “parent” plugins. In our case, we’re customizing aspects of api-plugin-products, so we named our plugin api-plugin-products-volume.

Link the plugin

Next we have to load our new plugin by linking it inside the Open Commerce Docker container. While you can do this in production mode, it’s better to do it in development mode, which allows you to add and remove plugins on the fly. 

To enable development mode, navigate to your reaction-development-platform directory and run make dev-reaction.

Note: Starting development mode takes a couple minutes—much longer than production mode. Development mode builds an editable Docker image on demand, while production mode uses an uneditable, pre-built image. 

From here, we’ll change directories to reaction-development-platform/reaction and add our plugin to the list in plugins.json

plugins.json

JSON
{
  ...
  "productsVolume": "@leaderpack/api-plugin-products-volume",
  ...
}

To link our plugin to the Open Commerce API Docker container, we’ll run bin/package-link api-plugin-products-volume. If you need to link a plugin that is not in the api-plugins folder, add the path to your plugin as the second argument of bin/package-link.

The API will restart automatically with the new plugin loaded and ready to use. When you make edits to a plugin in development mode, the Open Commerce API service will automatically refresh to reflect those edits. 

You can verify that the plugin has loaded in the logs by running docker-compose logs -f api, or in the admin dashboard under Settings > System information.

Add functions

So we know that our new plugin is running, but we haven’t programmed it to do anything yet. Now we can start to add functionality—and since Open Commerce is in development mode, our plugin will reload every time we modify it.

Open Commerce offers several ways to extend its functionality and API. Our plugin will use two approaches: functionsByType, which overrides or extends existing functions, and graphQL, which adds queries and mutations to the GraphQL API. 

Since our goal is to add a volume attribute to product variants, we’ll need to extend the existing product and catalog SimpleSchema to include this new field. First, we’ll add a startup function to extend the two SimpleSchemas, allowing them to be validated and saved to MongoDB:

startup.js

Node.js
function myStartup(context) {
  context.simpleSchemas.ProductVariant.extend({
    volume: {
      type: Number,
      min: 0,
      optional: true
    }
  });

  context.simpleSchemas.CatalogProductVariant.extend({
    volume: {
      type: Number,
      min: 0,
      optional: true
    }
  })
}

We’ll also extend the function that publishes products to the catalog, adding extra steps to publishProductToCatalog. The new myPublishProductToCatalog function parses our products, gets the new volume attribute, and adds it to the corresponding catalog variant in preparation for publishing it to the catalog:

index.js

Node.js
function myPublishProductToCatalog(catalogProduct, { context, product, shop, variants }) {
 catalogProduct.variants && catalogProduct.variants.map((catalogVariant) => {
   const productVariant = variants.find((variant) => variant._id === catalogVariant.variantId);
   catalogVariant.volume = productVariant.volume || null;
 });
}

Then we’ll instruct the catalogs plugin to looks for the myPublishProductToCatalog function by type, making it the key to communicating data from the new field to the catalog:

index.js

Node.js
export default async function register(app) {
  await app.registerPlugin({
    label: "Product Dimensions",
    name: "products-dimensions",
    version: pkg.version,
    functionsByType: {
      startup: [myStartup]
      publishProductToCatalog: [myPublishProductToCatalog]
    },
  });
}

Extend the GraphQL API

Our custom plugin is linked, registered, and has a startup function, so now it’s time to extend the GraphQL API. We’ll do that by creating a schema.graphql file in the plugin’s src/ directory and adding type extensions:

schema.graphql

GraphQL
extend type ProductVariant {
  volume: Float
}

extend input ProductVariantInput {
  volume: Float
}

extend type CatalogProductOrVariant {
  volume: Float
}

Back in our plugin’s src/index.js file, we can import the new schema using importAsString and add it to the app.registerPlugin options:

src/index.js

Node.js
import importAsString from “@reactioncommerce/api-utils/importAsString.js”;

const mySchema = importAsString(./schema.graphql”);

export default async function register(app) {
  await app.registerPlugin({
    label: "Product Dimensions",
    name: "products-dimensions",
    version: pkg.version,
    functionsByType: {
      startup: [myStartup]
      publishProductToCatalog: [myPublishProductToCatalog]
    },
    graphQL: {
      schemas: [mySchema]
    }
  });
}

That’s it! We can see the plugin in action by using a GraphQL mutation to update a product variant with a volume attribute:

Volume mutation

GraphQL
mutation {
  updateProductVariant(input: {
    variantId: "{encoded-variant-id}",
    shopId: "{encoded-shop-id}",
    variant: {
      volume: 200
    }
  }) {
    variant {
      volume
    }
  }
}


{
  "data": {
    "updateProductVariant": {
      "variant": {
        "volume": 200
      }
    }
  }
}

And once that variant is published, the new volume field will be available on the catalog product, thanks to the publishProductToCatalog function:

Volume query

GraphQL
query {
  catalogItemProduct(
    shopId: "{encoded-shop-id}",
    slugOrId: "{encoded-slug-or-id}") {
    product {
      variants {
        volume
      }
    }
  }
}
 

{
  "data": {
    "catalogItemProduct": {
      "product": {
        "variants": [
          {
            "volume": 200
          }
        ]
      }
    }
  }
}

We’ve done everything we need to add volume data to our backpacks and query that information across our store. That’s useful to Leader of the Pack’s store administrators, but we also need to get that information to customers. This will require some design work on our product pages, which are dependent on the storefront plugin that we’re using. Generally, this will involve making sure that our product query includes the new volume field and that the information is passed to the render() function in the file that describes our product detail page. For more information, see the Creating and Organizing Products doc.