Plugins Quickstart

This tutorial walks through the mechanics of creating and publishing a Talk plugin. Along the way I call out some particular habits and techniques that I employ. If you have other practices that you find valuable, please don’t hesitate to contribute!

We will create a plugin that exposes a route that allows assets to be created or updated.

Setup the environment

Before I begin working on the plugin, I’ve installed Talk from source.

Watch the Server

In a terminal, I run yarn dev-start. This:

  • starts my server, showing plugin and configuration information
  • restarts it when I save files
  • shows my temporary console.log() statements
  • shows real time access logs
  • shows verbose debug output if enabled (more on this later)

Watch the Client build process

In a separate terminal I run yarn build-watch. This:

  • builds the client side javascript bundles
  • watches relevant files and rebuilds the bundle on change
  • displays compile time errors, including (the many) syntax errors I cause

If you need to run yarn install, you will see missing module error messages here.

Watch from the Browser

I open up http://localhost:3000 in a web browser and see the default comment stream. I then open the dev tools console, which:

  • shows any run time errors/warnings generated on the front end.
  • shows any temporary console.log() statements I add during development.

I also often toggle to the Network Tab to see:

  • which files are being loaded
  • requests sent from my front end code, including headers, the payload/queries sent and the data returned

I strongly recommend taking the time to fully explore all the features of your browser’s dev tools!

Create a home for my new plugin

My goals for this tutorial are to:

  • build this plugin locally
  • use source control and publish for collaboration
  • publish the plugin as an npm library

Create a repo

I create a new repo called talk-plugin-asset-manager. (I use github, but this you could store this anywhere, bitbucket, svn, etc…)

make sure to respect the naming convention talk-plugin-*. This will allow for easy identification of the repo and, eventually, easy searching on npm.

Set up a local file structure

In a blatant rip off from the Golang community, I create an environment var to hold the path to the root of my Coral directory. This allows absolute pathing.

export CORALPATH=/path/to/my/coral/root/dir

I like to put my plugins in a directory next to talk, but you could put this anywhere.

cd $CORALPATH
git clone https://github.com/jde/talk-plugin-asset-manager.git

Register your plugin

Add the plugin to the plugins.json file:

{
  "server": [
    ...
    "talk-plugin-asset-manager"
  ],
  "client": [
    ...
    // no client side components so I won't add it here
  ]
}

But wait! Talk looks in talk/plugins/[plugin-name] for plugin code. Why couldn’t we just add that plugin there?

We could have.

This would make it a little easier to register, but a lot harder to cleanly manage in version control. In order to avoid it being sucked into your Talk repo, you would have to manually .gitignore it or use sub modules or something similar.

As a user of a Linux_y_ os, I prefer to create a symbolic link.

cd $CORALPATH/talk/plugins
ln -s $CORALPATH/talk-plugin-asset-manager

Now, as far as Talk knows, our plugin is right there in the folder. Git is wise, however, and will not include it in the Talk repo. Best of all, our yarn dev-start based watch statement follows symbolic links and will restart our sever each time a file is saved.

Create the initial index file

All plugins contain server and/or client index files, which export all plugin functionality.

// talk-plugin-asset-manager/index.js
module.exports = {};

Build the feature!

Now that the plugin is set up I can get down to writing the feature. My goal is to allow my CMS to push new assets as they are created into Talk. To accomplish this, I will create a POST endpoint using Talk’s route api.

Create a route

When designing my api, I want to be careful to avoid conflicts with not only the existing Talk api, but other plugins in the open source ecosystem that may be creating routes. To do this, I’ll follow the golden rule of creating universals with plugins:

Always namespace all universals with your plugin’s unique name.

To ensure everything is hooked up, I’ll log the request body (POST payload in this case) to the console and echo it as the response:

// talk-plugin-asset-manager/index.js
module.exports = {
  router(router) {
    router.post('/api/v1/asset-manager', (req, res) => {
      console.log(req.body);
      res.json(req.body);
    });
  }
}

When I save this file, I reflexively check my console to be sure that the server restarts.

To test that this works, I can:

$ curl -H "Content-Type: application/json" -X POST -d '{"url":"http://localhost:3000/my-article","title":"My Article"}' http://localhost:3000/api/v1/asset-manager
{"url":"http://localhost:3000/my-article","title":"My Article"}

After hitting the endpoint, I can also look at the terminal running yarn dev-start and see my console.log() and the access log:

{ url: 'http://localhost:3000/my-article',
  title: 'My Article' }
POST /api/v1/asset-manager 200 1.379 ms - 68

Save the asset

When I save this asset, I will use Talk’s asset model.

Mongo has a handy method findOneAndUpdate that will take care determining whether or not this asset exists, then either updating or inserting it. Whenever possible, we recommend using these atomic patterns that prevent multiple queries to the db and the efficiency problems and race conditions that they cause.

// talk-plugin-asset-manager/index.js

const AssetModel = require('models/asset');

module.exports = {
  router(router) {
    router.post('/api/v1/asset-manager', (req, res) => {

      const asset = req.body;
      const update = {$setOnInsert: {url: asset.url}};

      AssetModel.findOneAndUpdate(asset, update, {
        new: true,
        upsert: true,
        setDefaultsOnInsert: true
      })
      .then((asset) => res.json(asset));
    });
  }
}

I can now run the curl command as before and see that the response contains a complete asset document!

In addition, I can change the title in the json payload and verify that the id is the same, indicating that the record was updated!

Lastly, I can see the asset in the admin panel at http://localhost:3000/admin as well as in my database.

We have an alpha version of our plugin!

More work to be done

The purpose of this tutorial is to follow the full lifecycle of a plugin, from conception through publication into deployment. With that in mind we’ll move forward with this alpha version.

Some things to make this production ready:

It is important to realize that when you’re writing a Talk plugin you are writing a program that may be touched by other devs and could grow in size and complexity. Bring your best engineering sensibilities to bear.

Publishing the plugin

Publish to npm

In order to register your published plugin, you will need to publish it to npm.

Once the package is published, update plugins.json to use the published plugin:

{
  "server": [
    // ...
    {"talk-plugin-asset-manager": "^0.1"}
  ],
  // ...
}

Finally, run the reconcile script to install the plugin from npm.

$ bin/cli plugins reconcile

Once you’ve taken this step, anyone can register your plugin into their Talk server! Thank you for contributing to the open source community!

Publish to version control

This plugin is open source, so I’m also going to publish it to github and cut a release that mirrors the npm release.

Done!