DevPul Open DevPul

Plugins

A plugin adds a tab for some event kinds. It is an ES module served from a URL: add the URL in Settings > Plugins and the tab appears. The tab lists the plugin's events with the same filter, pause and keyboard navigation as the built-in tabs, and the plugin decides what the list says and what the detail pane shows.

Plugins run in the browser and only change how events look; the app sends them with Devpul.emit. Extending DevPul shows the whole path with Riverpod and Bloc.

A minimal plugin

// cart.js
export default {
  id: 'cart',
  label: 'Cart',
  kinds: ['cart.updated', 'cart.cleared'],
  summarize: (e) => `${e.data.items} items, ${e.data.total}`,
  render(el, e) {
    el.textContent = `Total: ${e.data.total}`;
  },
};

The app side is plain events:

Devpul.emit('cart.updated', {'items': 3, 'total': 42.5});

API

Field Type
id string Unique. Registering the same id again replaces the plugin.
label string Tab name.
kinds string[] or (kind) => boolean Event kinds the tab takes. Claimed kinds leave the Events tab.
summarize (event) => string Optional. One line in the list.
render (el, event, context) => void | (() => void) Optional. Fills el for the selected event. May return a cleanup function. Without it, the detail shows the event as a JSON tree.

An event is {key, kind, data, ts, session, run}. data holds the fields the app sent plus the envelope (v, session, seq, ts, tags). run identifies one launch or hot restart of one app.

context gives:

Member
context.events Every event the plugin matches, oldest first. Read it in render; it is current at the time you read it.
context.call(method, params) Calls a VM service method on the app that sent the event and returns the result. Service extensions (ext.*) get the event's isolate unless you pass isolateId.

render runs again when the selection changes. Use the page's CSS variables, such as var(--muted), var(--accent) and var(--border), and the code class for preformatted text, so the plugin follows the theme.

Talking to the app

context.call reaches any service extension the app registers with dart:developer:

registerExtension('ext.myapp.flags', (method, params) async {
  if (params['set'] != null) flags.toggle(params['set']!);
  return ServiceExtensionResponse.result(jsonEncode(flags.toJson()));
});
render(el, e, context) {
  const button = document.createElement('button');
  button.textContent = 'Toggle beta';
  button.onclick = async () => {
    const flags = await context.call('ext.myapp.flags', { set: 'beta' });
    button.textContent = `beta: ${flags.beta}`;
  };
  el.append(button);
}

For plain buttons with no custom UI, Devpul.action is simpler.

Example: Routes

DevPul serves one example, a plugin that shows the navigation stack after each event from DevpulNavigatorObserver. Add this URL in Settings > Plugins:

https://devpul.saradgajurel.com.np/plugins/routes.js

The source is a good starting point.

Other module shapes

The default export can be one plugin, a list of plugins, or a function that receives the API and registers plugins itself:

export default (devpul) => {
  devpul.register({ id: 'a', label: 'A', kinds: ['a'] });
  devpul.register({ id: 'b', label: 'B', kinds: (k) => k.startsWith('b.') });
};

The same API is on window.devpul ({version, register}), so you can try a plugin from the browser console. version is 2.

Developing a plugin

Serve the folder with any static server that sets CORS headers, then add the file's URL in Settings:

npx http-server --cors -p 8090
# Settings > Plugins: http://127.0.0.1:8090/cart.js

Plugins load once per page; reload DevPul after editing. Load errors show under the plugin URLs in Settings.

Sharing a plugin

Any static host that sends CORS headers and a JavaScript content type works: GitHub Pages, your own site, or a CDN that mirrors GitHub, such as https://cdn.jsdelivr.net/gh/<user>/<repo>/state.js. raw.githubusercontent.com serves files as plain text, which browsers refuse to run as modules.

Security

A plugin runs with full access to the page: every event, header and body DevPul has seen. Load only code you trust, from hosts you control.