Skip to content

Creating plugins

This page shows how to write a GrapesJS plugin for the editor, a Silex server plugin, and a publication transformer. To load them, see Using plugins.

Licensing: Plugins are not considered derivative works of Silex under the AGPL license. You can use any license for your plugins.

To change Silex itself, see the contribution guide. The official @silexlabs/grapesjs-* plugins live in the Silex repository, under grapesjs-plugins/.

Two plugin formats

Format Signature Runs in Loaded by
GrapesJS plugin (editor, opts) => {} the browser config.grapesJsConfig.plugins in the client config
Silex plugin async (config, options) => ({}) the browser or Node config.addPlugin(), SILEX_CLIENT_CONFIG, SILEX_SERVER_CONFIG

A Silex plugin receives the config object: ClientConfig in the browser, ServerConfig in Node. It can return an object, which is merged into the config. The client config file and the server config file are Silex plugins too.

GrapesJS plugin

// hello-block.js
export default function helloBlock(editor, opts) {
  editor.Blocks.add('hello-block', {
    label: opts.label ?? 'Hello block',
    category: 'Elements',
    content: '<p class="hello">Hello</p>',
  })
}

It uses the GrapesJS API: editor.Blocks, editor.Commands, editor.Panels, editor.on(), and so on. Load it in the client config:

// client-config.js
import helloBlock from '/my-plugins/hello-block.js'

export default async function (config) {
  config.grapesJsConfig.plugins.push(helloBlock)
  config.grapesJsConfig.pluginsOpts[helloBlock.toString()] = { label: 'Hello' }
  return {}
}

The path /my-plugins/hello-block.js must be served by Silex. The next section does that.

Silex server plugin

A server plugin adds its routes when the server starts, in the startup-start event. The event gives the Express app:

// server-config.js, set SILEX_SERVER_CONFIG to its path
module.exports = async function (config, options) {
  config.on('startup-start', ({ app }) => {
    app.get('/hello', (req, res) => {
      res.json({ message: 'Hello from my plugin' })
    })
  })
  return {}
}

Write the file in CommonJS (module.exports, require), like server/deploy/.silex.js. Use your own path prefix for routes: /api/ belongs to Silex.

Serve a file for the editor

The same handler can serve the GrapesJS plugin above, so the client config can import it:

const { join } = require('node:path')

module.exports = async function (config) {
  config.on('startup-start', ({ app }) => {
    app.get('/my-plugins/hello-block.js', (req, res) => {
      res.sendFile(join(__dirname, 'my-plugins/hello-block.js'))
    })
  })
  return {}
}

Listening to server events

config is an event emitter. The events, in server/events.ts:

Event Name in ServerEvent Argument
startup-start STARTUP_START { app }, before the server listens
startup-end STARTUP_END { app }, the server listens
publish-start PUBLISH_START the publication data: files, pages, assets, styles, settings…
publish-end PUBLISH_END nothing, or the Error. Sent when the hosting connector has started its work: FTP and GitLab go on in the background
store-start WEBSITE_STORE_START { websiteId, websiteData, connectorId }
store-end WEBSITE_STORE_END null, or the Error
asset-store-start WEBSITE_ASSET_STORE_START { files, websiteId, connectorId }
asset-store-end WEBSITE_ASSET_STORE_END null, or the Error
module.exports = async function (config) {
  config.on('publish-end', (error) => {
    console.log('Publication', error ? `failed: ${error.message}` : 'started')
  })
  return {}
}

Add a connector

A storage connector stores the websites and their assets. A hosting connector publishes them. They implement the StorageConnector and HostingConnector interfaces of server/connectors/connectors.ts:

Interface Members
both connectorId, connectorType, displayName, icon, color, background, disableLogout?, getOAuthUrl, getLoginForm, getSettingsForm, isLoggedIn, setToken, logout, getUser, getOptions
StorageConnector listWebsites, readWebsite, createWebsite, updateWebsite, deleteWebsite, duplicateWebsite, writeAssets, readAsset, deleteAssets, getWebsiteMeta, setWebsiteMeta
HostingConnector publish, getUrl

Register the connector in the startup-start handler, with config.addStorageConnector() or config.addHostingConnector(). .silex.js, loaded after your file, sets the connector lists from STORAGE_CONNECTORS and HOSTING_CONNECTORS. This example reuses the file system connector of Silex with another folder. The Silex classes are in the dist/server/ folder of the Silex install, /silex/dist/server/ in the Docker image:

const { FsStorage } = require('/silex/dist/server/server/connectors/FsStorage')

class MyStorage extends FsStorage {
  connectorId = 'my-storage'
  displayName = 'My storage'
}

module.exports = async function (config) {
  config.on('startup-start', () => {
    config.addStorageConnector(new MyStorage(config, { path: '/data/my-storage' }))
  })
  return {}
}

The built-in connectors are good examples: FtpConnector, GitlabConnector, GitlabHostingConnector and DownloadConnector in server/plugins/, FsStorage and FsHosting in server/connectors/.

Publication transformer

A transformer is an object, added in the client config with config.addPublicationTransformers(). It is not a GrapesJS plugin. It runs in the browser when the user publishes:

// client-config.js
export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'html') {
        return { ...file, content: file.content.replace('</body>', '<!-- published with Silex --></body>') }
      }
      return file
    },
  })
  return {}
}

The allowed methods are renderComponent, renderCssRule, transformFile, transformPermalink and transformPath. Any other key throws an error. See Publication transformers.

See also

Edit this page on GitLab