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.