Basics
Features
Best Practices
Addon Development
Related Docs
Dark Mode

#Getting Started

The MercuryCMS Addon System is a powerful platform that gives you access to all of Mercury's features and allows you to extend it with your own Screens, Collections, Components, and functionality.

Before you get started, you should know how Addons work and interact with MercuryCMS.

  • Addons are installed via package files
  • MercuryCMS provisions a directory for the Addon's code, file storage, and a database
  • The Addon Package has a manifest that tells MercuryCMS about the Addon such as the name and what processes it will run
  • MercuryCMS launches the Addon's processes which must continually run alongside MercuryCMS
  • Addons are servers that talk to MercuryCMS and provide an API for MercuryCMS to talk to
  • Addons also serve their provided Screens (such as their settings) as HTML/CSS/JS
  • Addons provide their own API for their Screens to talk to

If this seems a bit complicated, don't worry, it's actually a very simple and flexible way to add functionality to Mercury.

#Demo Addon

To make things easy, we'll use the static-cms-addon npm package to write a simple demo Addon. This will gloss over a lot of details which are explained later in the rest of the Addon Development documentation.

First, let's make a new directory and a single file called addon.json.

{
  "name": "demo",
  "displayName": "Demo",
  "description": "A quick demo addon",
  "author": "Allister Aligerum",
  "version": "1.0.0",
  "dependencies": [
    {"type": "node", "path": "/"}
  ],
  "settingsPath": "/settings",
  "processes": {
    "main": {
      "type": "node",
      "main": "/index.js",
      "ports": 1,
      "interface": true
    }
  }
}

This manifest file tells MercuryCMS everything it needs to know about our Addon:

  • The name of the Addon (which must only contain lowercase characters, numbers, and dashes) and its display name (used in the Addon list)
  • MercuryCMS should install the Addon's dependencies by running npm install in the root of this directory when the Addon is installed
  • The Settings button should navigate to /addons/demo/settings (which will be an interface Screen our Addon will provide that we'll make in a moment)
  • MercuryCMS should navigate to our Addon's installation directory and run node index.js to start the Addon
  • That process that it starts will require 1 port to talk to MercuryCMS and will provide the Addon's interface Screens the user can see

#Dependencies

The only dependency this Addon will have is the static-cms-addon npm package. We can run npm init and then npm i @mercury-cms/static-cms-addon.

#Make the Addon Server

Now let's make an index.js file. This file will be run when MercuryCMS starts our Addon. Here, we will start an Express server to serve our Addon's interface screens and provide an API to communicate with MercuryCMS.

// index.js

import cms from 'static-cms-addon'

let app = cms.createServer()
app.use(cms.static('public'))
app.listen(cms.getPort(), () => {
  console.log(`Demo Addon running on port ${cms.getPort()}`)
})

This will serve html files from the public directory, which we haven't created yet, So let's do that. Create a public directory, then another directory called settings inside of that, and put an index.html file in it.

<!-- public/settings/index.html --->

<html>
  <head>
    <title>Demo</title>
  </head>
  <body>
    <p>This is our Settings Screen!</p>
  </body>
</html>

Once our Addon is installed, any page routes the user tries to access from the interface that start with /addons/demo will be proxied to our Addon's server. In this case, when they install the Addon and click the Settings button, Mercury will send them to /addons/demo/settings which will be routed to /settings at our Express server. That will serve this HTML document.

#Packaging The Addon

Now let's package this Addon and install it. Let's make a file called build.js and run it. This will package our Addon into a single file that can be installed into MercuryCMS.

// build.js

import cms from 'static-cms-addon'

cms.packageAddon()

This will create a file in the parent directory of your Addon's source code called demo-1-0-0.addon.zip. Now we can go to the Addons screen and install it. Once it's running, you can click the Settings button to see the Settings page we created.

#Saving and Loading Settings

Now let's make our settings screen actually store the Addon's settings. We'll need to store the settings in the database MercuryCMS provides us. Update/create the following files:

<!-- public/settings/index.html --->

<html>
  <head>
    <title>Demo</title>
    <link rel="stylesheet" href="/api/addon/style.css">
    <link rel="stylesheet" href="settings/style.css">
  </head>
  <body>
    <div class="settings-page">
      <div class="app-card">
        <p>This is our Settings Screen!</p>
        <div class="site-input-group">
          <div class="label">License Key</div>
          <input class="site-input" id="license-input">
        </div>
        <div class="buttons">
          <div class="app-button" onclick="handleSave()">Save</div>
        </div>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
    <script src="/api/addon/script.js"></script>
    <script src="settings/script.js" type="module"></script>
  </body>
</html>
/* public/settings/style.css */

.settings-page {
  padding: 16px;
}

.settings-page .app-card .buttons {
  margin-top: 8px;
}
// public/settings/script.js

let response = await axios.get(`${cms.getUrl()}/api/settings`)
document.getElementById('license-input').value = response.data.settings.licenseKey

window.handleSave = async function () {
  let licenseKey = document.getElementById('license-input').value
  await axios.put(`${cms.getUrl()}/api/settings`, {licenseKey})
  cms.queueNotification({body: 'License Key saved'})
}
// index.js

import cms from 'static-cms-addon'

// init settings
if (!await cms.database.collection('settings').findOne()) {
  await cms.database.collection('settings').insertOne({
    licenseKey: '',
  })
}

// add sidebar
await cms.setSidebarItems([
  {icon: 'star', name: 'Demo', path: '/settings'},
])

// create server
let app = cms.createServer()
app.use(cms.static('public'))

// get settings for frontend
app.get('/api/settings', async (req, res) => {
  let settings = await cms.database.collection('settings').findOne()
  res.send({settings})
})

// update settings from frontend
app.put('/api/settings', async (req, res) => {
  await cms.database.collection('settings').updateOne({}, {$set: {licenseKey: req.fields.licenseKey}})
  res.send()
})

// start the server
app.listen(cms.getPort(), () => {
  console.log(`Demo Addon running on port ${cms.getPort()}`)
})

You can now build and install this Addon and you have a functioning demo that looks like a native part of MercuryCMS. Here are some notes about what the Addon is now doing:

  • settings/index.html includes /api/addon/style.css which is a stylesheet MercuryCMS provides to allow you to make native-looking interfaces that respects the user's theme preference and Dark Mode settings
  • settings/index.html includes /api/addon/script.js which is a script file that provides a cms object you can use to get the URL to talk to your Addon's API
  • settings/index.html is including style.css and script.js which is being provided by the Addon's server
  • settings/script.js is populating the input field with the saved setting, saving the new setting to the API provided by the Addon, and showing a notification using the cms object provided by the /api/addon/script.js script
  • index.js is now providing a sidebar item
  • index.js initializes settings in the database if they don't exist and allows the frontend to retrieve them and update them

From here, you can dig into the rest of the documentation. The Addon System provides a lot of powerful tools you can use to integrate with all of the features MercuryCMS provides. Have fun!