Writing a no-build plugin
The following guide will provide a short tutorial on how to create a single page no-build plugin for JBrowse 2.
Pre-requisites
- you can run an instance of JBrowse 2 on the web, see any of our quickstart guides for details
- a stable and recent version of node
- basic familiarity with the command line and navigating the file system
What is the difference between a no-build plugin and a "regular" plugin?
A "regular" JBrowse plugin often uses our plugin template
https://github.com/GMOD/jbrowse-plugin-template which uses rollup to compile
extra dependencies that your plugin might use.
In contrast, "no-build" plugins have no build step and can be hand edited. This can be useful for adding extra jexl config callbacks for making extra config callbacks or similar modifications.
Writing a "no-build" plugin
Example use case: Adding a jexl callback function which you can use in your config
A common method for a no-build plugin might be making a custom function that you
can use to simplify jexl callbacks in your config. We will create a file
myplugin.js, which will export a single class.
myplugin.js
export default class MyPlugin {
name = 'MyPlugin'
version = '1.0'
install(pluginManager) {
pluginManager.jexl.addFunction('customColor', feature => {
if (feature.get('type') === 'exon') {
return 'red'
} else if (feature.get('type') === 'CDS') {
return 'green'
}
})
}
configure(pluginManager) {}
}
Put this file myplugin.js in the same folder as your config file, and then,
you can refer to this plugin and the custom function you added in your
config.json.
{
"plugins": [
{
"name": "MyPlugin",
"esmLoc": {
"uri": "myplugin.js"
}
}
],
"tracks": []
}
Example use case: Adding a global menu item
Another example of a no-build plugin is to add menu items or minor extension
points. Here, we're going to add a menu item using the configure method in the
plugin class.
myplugin.js
export default class MyPlugin {
name = 'MyPlugin'
version = '1.0'
install() {}
configure(pluginManager) {
// this is called in the web worker as well, which does not have a
// rootModel, so check for existence of pluginManager.rootModel before
// continuing
if (pluginManager.rootModel) {
// adding a new menu to the top toolbar
pluginManager.rootModel.insertMenu('Citations', 4)
// appending a menu item to the new menu
pluginManager.rootModel.appendToMenu('Citations', {
label: 'Cite this JBrowse session',
onClick: session => {
/* do nothing for now, see below for example */
},
})
}
}
}
Importing with jbrequire
Because our plugin is not going to be built with any dependencies, the process for referencing external libraries is a little different.
If a package you need to use is found within the JBrowse core project, a special
function jbrequire can provide your plugin access to these packages. Click
here
for a full list of packages accessible through jbrequire. Using jbrequire
might look like this:
const { types } = pluginManager.jbrequire('@jbrowse/mobx-state-tree')
This would provide the functionality of @jbrowse/mobx-state-tree through that value.
Complete example
Example
esmplugin.js
export default class MyPlugin {
name = 'MyPlugin'
version = '1.0'
install(pluginManager) {
// here, we use jbrequire to reference packages exported through JBrowse
const { ConfigurationSchema } = pluginManager.jbrequire(
'@jbrowse/core/configuration',
)
const WidgetType = pluginManager.jbrequire(
'@jbrowse/core/pluggableElementTypes/WidgetType',
)
const { ElementId } = pluginManager.jbrequire(
'@jbrowse/core/util/types/mst',
)
const { types } = pluginManager.jbrequire('@jbrowse/mobx-state-tree')
const React = pluginManager.jbrequire('react')
// this is our react component
const CiteWidget = props => {
// React.createElement can be used to add html to our widget component.
// We write out raw React.createElement code because JSX requires a build
// step and can't be used very easily in the no build plugin context
const header = React.createElement(
'h1',
null,
'Cite this JBrowse session',
)
const content = React.createElement(
'p',
null,
'Diesh, Colin, et al. "JBrowse 2: A modular genome browser with views of synteny and structural variation." bioRxiv. 2022.',
)
return React.createElement('div', null, [header, content])
}
// we're adding a widget that we can open upon clicking on our menu item
pluginManager.addWidgetType(() => {
// adding a widget to the plugin
return new WidgetType({
name: 'CiteWidget',
heading: 'Cite this JBrowse session',
configSchema: ConfigurationSchema('CiteWidget', {}),
stateModel: types.model('CiteWidget', {
id: ElementId,
type: types.literal('CiteWidget'),
}),
// we're going to provide this component ourselves
ReactComponent: CiteWidget,
})
})
}
configure(pluginManager) {
if (pluginManager.rootModel) {
pluginManager.rootModel.insertMenu('Citations', 4)
pluginManager.rootModel.appendToMenu('Citations', {
label: 'Cite this JBrowse session',
onClick: session => {
// upon clicking on this menu item, we need to add and show our new widget
const widget = session.addWidget('CiteWidget', 'citeWidget', {})
session.showWidget(widget)
},
})
}
}
}
Then in your config you can reference it using the "esmLoc" function
{
"plugins": [
{
"name": "MyPlugin",
"esmLoc": {
"uri": "esmplugin.js"
}
}
],
"tracks": []
}
Result
With JBrowse running and the "Citation" plugin from above added to your config, your JBrowse session should look like the following:

Conclusion and next steps
Congratulations! You built and ran a single file no-build plugin in JBrowse.
Have some questions? Contact us through our various communication channels.
Note: JSX syntax
Writing React code without JSX is more verbose since JSX requires a build step. If your plugin has dependencies or you prefer TypeScript, use the plugin template which includes a build step, bundler, and type checking.
Note: UMD vs ESM module syntax
This guide uses ESM modules (exporting a plain class), which all modern browsers support. For legacy browser compatibility you can also use UMD modules — see this example, which defines a specific global variable rather than exporting a class.