A declarative browser plugin loader and runtime for CoCreate. It loads registered JavaScript and CSS resources, exposes their browser globals to the CoCreate AST, initializes plugin instances on DOM elements, and connects those instances to standard element value APIs.
For interactive examples, visit the CoCreate Plugins documentation.
- Architectural Overview
- Installation
- Registering Plugin Resources
- Using the
pluginAttribute - AST Execution Model
- Element Value Integration
- Programmatic API
- Resource Loading
- Error Handling
- Security
- Announcements
- Roadmap
- How to Contribute
- License
Plugin results are not automatically assigned to the host element. To retain a returned instance, explicitly assign it using AST:
<div plugin='{
"$this.myInstance": {
"$ExamplePlugin": ["$this", {}]
}
}'></div>Here $this identifies the host element and myInstance names the property to
write. The nested initializer executes first, then its returned value is
assigned to that property. A global plugin call that needs no retained result
can run without this assignment. Code that previously relied on automatic element.PluginName
storage must explicitly assign and reference the instance instead.
@cocreate/plugins connects three parts of the browser runtime:
- A plugin definition in
window.CoCreateConfig.pluginsidentifies the JavaScript and CSS resources. - A DOM element's
pluginattribute contains an AST configuration. - AST executes the configuration. Results are stored only where the configuration explicitly assigns them.
CoCreateConfig.plugins
│
├── load JavaScript and CSS
│
▼
window.PluginName
│
├── AST executes plugin configuration
│
▼
result (optionally assigned through AST)
Plugin operations execute sequentially. Plugin names continue to refer to their global libraries; constructor results do not automatically replace those references or attach to the element.
The module observes [plugin] elements automatically, including elements added or updated after the initial page load.
npm install @cocreate/pluginsyarn add @cocreate/plugins<script src="https://cdn.CoCreateJS.com/plugins/latest/CoCreate-plugins.min.js"></script>When using the package as a module:
import plugins from "@cocreate/plugins";Plugin resource definitions live under window.CoCreateConfig.plugins. The property name should match the global name created by the external library.
window.CoCreateConfig = {
plugins: {
SimpleBar: {
js: "https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.min.js",
css: "https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.css"
},
FullCalendar: {
js: "https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js"
}
}
};Both js and css accept one resource or an array of resources. A resource can be a URL string or an object:
window.CoCreateConfig.plugins.Example = {
js: [
{
src: "https://cdn.example.com/example.min.js",
crossOrigin: "anonymous",
integrity: "sha384-..."
}
],
css: [
{
href: "https://cdn.example.com/example.min.css",
crossOrigin: "anonymous",
integrity: "sha384-..."
}
]
};Plugins can also be registered at runtime:
import { registerPlugin } from "@cocreate/plugins";
registerPlugin("SimpleBar", {
js: "https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.min.js",
css: "https://cdn.jsdelivr.net/npm/simplebar@latest/dist/simplebar.css"
});Registration describes where a library is loaded from. Initialization behavior belongs in the element's plugin attribute.
Use an explicit AST call to initialize a plugin and a dotted destination to retain its result on the element.
<div
plugin='{
"$this.SimpleBar": { "$SimpleBar": ["$this", {}] }
}'
style="max-height: 300px">
</div>This is conceptually equivalent to:
element.SimpleBar = new SimpleBar(element, {});$this resolves to the element containing the plugin attribute.
For a global function that receives a single options object:
<button
plugin='{
"$this.toast": {
"$Toastify": {
"text": "Saved successfully",
"duration": 3000
}
},
"$this.toast.showToast": []
}'>
Show notification
</button>Some libraries expose constructors as properties of a global namespace. A single nested member identifies the constructor to invoke:
<div
id="calendar"
plugin='{
"$this.FullCalendar": {
"$FullCalendar.Calendar": [
"$this",
{ "initialView": "dayGridMonth", "editable": true }
]
},
"$this.FullCalendar.render": []
}'>
</div>The first key explicitly creates and stores the instance:
element.FullCalendar = new window.FullCalendar.Calendar(element, options);The next key operates on that resolved instance:
element.FullCalendar.render();A plain attribute value invokes the registered global without arguments:
<div plugin="ExamplePlugin"></div>Use the JSON form whenever the plugin requires the element, options, or additional operations.
The attribute can contain an array. Each object is processed in order:
<div
plugin='[
{ "$FirstPlugin": ["$this"] },
{ "$SecondPlugin": ["$this", {}] }
]'>
</div>Each JSON configuration object is passed unchanged to astAsync, once, with
the registered libraries and $this in its sandbox and the host element as
node. The plugin loader does not wrap individual entries or rewrite ordinary
keys into calls. Sibling entries share AST's evaluation scope, including values
created by earlier entries. Configuration arrays execute one block at a time.
The bare-name shorthand is expanded to { "$PluginName": [] }.
{
"instance": { "$FullCalendar.Calendar": ["$this", {}] },
"$this.calendar": "$instance",
"$instance.render": []
}JSON configs that previously relied on implicit plugin calls must use explicit
$PluginName or $PluginName.method keys. Ordinary keys declare AST data.
Execution follows these rules:
- Plugin globals are placed in the AST sandbox.
$thisis the element being initialized.- Arrays are passed as positional arguments.
- An object is passed as a single argument.
- Native classes are instantiated with
new. - Functions are called normally.
- A resolved top-level value becomes available to subsequent keys.
- No result is automatically attached to the element, including
$returnresults. - Use an explicit AST assignment to retain an instance on
$thisor another destination.
Ordinary non-plugin keys remain part of the AST program, so they can store intermediate results or participate in later operations.
<div
plugin='{
"instance": { "$ExamplePlugin": ["$this"] },
"items": { "$instance.getItems": [] },
"count": "$items.length"
}'>
</div>Top-level result keys do not automatically become element properties. Use an explicit assignment to store values on the element.
The reserved getValue and setValue keys connect a plugin instance to CoCreate's standard element value interface. Unlike initialization operations, these AST expressions are stored and evaluated only when the corresponding element method is called.
<div
id="calendar"
component
plugin='{
"FullCalendar": {
"Calendar": ["$this", { "events": [] }]
},
"FullCalendar.render": [],
"getValue": "$FullCalendar.getEvents",
"setValue": {
"$FullCalendar.addEvent": "$event.value"
}
}'>
</div>const events = document.querySelector("#calendar").getValue();The string method reference is invoked with no arguments. Asynchronous consumers can use:
const events = await document.querySelector("#calendar").getValueAsync();document.querySelector("#calendar").setValue({
title: "Planning meeting",
start: "2026-09-01"
});The runtime scope exposes:
| AST value | Description |
|---|---|
$event.value |
Value passed to element.setValue(value) |
$event.dispatch |
Optional dispatch argument passed to setValue |
$this |
Element containing the plugin instance |
$PluginName |
Resolved plugin instance |
Registers or replaces a resource definition in window.CoCreateConfig.plugins.
registerPlugin("ExamplePlugin", {
js: "https://cdn.example.com/plugin.js",
css: "https://cdn.example.com/plugin.css"
});Finds and initializes plugin configurations. It accepts an element, collection, array, or selector and returns a promise.
import { init } from "@cocreate/plugins";
await init(document.querySelector("#calendar"));
await init(document.querySelectorAll("[plugin]"));
await init("[plugin]");Calling init() directly is generally unnecessary because the module observes matching elements automatically. It is useful when application code needs to wait for plugin initialization to complete.
- JavaScript resources are cached by URL and loaded once.
- CSS resources already present in the document are not inserted again.
- Multiple resources are loaded concurrently with
Promise.allSettled(). - String resource definitions default to
crossOrigin="anonymous". - Object definitions can override
crossOriginor provideintegrity. - After loading, the runtime waits briefly for the expected browser global.
The configured name should match the library's browser global. A capitalized fallback is supported, so a configured simpleBar can resolve window.SimpleBar, but using the exact global name is recommended.
Resource failures are logged as warnings. Initialization fails when the expected global is still unavailable after loading:
[Plugin System] Plugin 'ExamplePlugin' is not available on window.
Invalid JSON, AST failures, constructor errors, and method errors are reported with the original plugin attribute for debugging.
Because initialization is asynchronous, application code that requires a completed instance should await init().
Plugin scripts execute with the same browser privileges as the application. Only register resources from trusted locations.
For third-party CDN resources:
- Pin versions instead of relying on floating tags such as
latest. - Provide Subresource Integrity metadata when available.
- Use an appropriate Content Security Policy.
- Review the library's data access and network behavior.
AST configuration controls how an already trusted plugin is initialized; it does not isolate untrusted JavaScript.
Updates are documented in the project CHANGELOG and releases.
See the open issues and pull requests for current development priorities.
Contributions are welcome. See CONTRIBUTING.md before opening an issue or pull request.
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.