Fixed extension points
Plugins extend the runtime at six named seams: database drivers, CSS frameworks, icon packs, callable libraries, UI behaviors, and visual widgets.
Xuda Developer Plugins
A Xuda plugin is a small npm package that follows a fixed layout and exports a fixed
set of shapes. The runtime loads the plugin by package name, reads its manifest, asks for fields or
methods or tabs, and invokes a runtime entry point inside the browser or on the server.
The same contract powers widget, ui, library,
graphics, framework, and dbs plugins.
Xuda separates the reactive runtime from the features it renders. The runtime knows how to mount apps, track datasource fields, route events, and render trees. It does not know how to draw a gauge, query CouchDB, send a Twilio SMS, format a date with Moment, or bootstrap Tailwind. Those are plugins. Each one plugs into a specific extension point, exposes a typed schema for its configuration, and declares a runtime function the platform can call.
Plugins extend the runtime at six named seams: database drivers, CSS frameworks, icon packs, callable libraries, UI behaviors, and visual widgets.
Every plugin declares its configurable surface as a fields or properties schema. The studio renders these directly as form controls.
The capabilities export tells the loader where the plugin is allowed to run: browser only, headless/SSR, or both. Mismatches short-circuit at render time.
Plugins can also declare tabs used by the studio to collect app-level setup (connection strings, API keys, theme configs). The runtime fetches them via func.utils.get_plugin_setup.
Runtime code lives in runtime.mjs and is imported on first use. Studio-time code lives in index.mjs and is loaded when the studio needs the schema.
Plugins are plain npm packages. Bump version, run npm publish, and the marketplace picks up the new build. Apps pin versions through their plugin list.
The naming convention @xuda.io/xuda-<category>-plugin-<name> is not cosmetic. The runtime
and studio dispatch on the category segment to decide which contract to apply, which UI to show in the studio,
and which extension seam to call.
A dbs plugin teaches the runtime how to speak to a storage backend. It exports a
tabs(doc) function for connection setup and a server.mjs module with CRUD
primitives (read, upsert, index checks, schema hooks). Examples:
xuda-dbs-plugin-couchdb, xuda-dbs-plugin-mysql,
xuda-dbs-plugin-xuda.
A framework plugin owns theming and the global UI primitives: toasts, modals, loaders,
popovers, pages. It exports a core class with init, refresh,
renderEngineProperties, plus bind, toast, loader,
popover, popup, modal, and page classes.
Examples: xuda-framework-plugin-tailwind, xuda-framework-plugin-ionic.
A graphics plugin exports tabs arrays of named SVG assets grouped by
label. The studio's icon picker reads those directly. Examples:
xuda-graphics-plugin-feather, xuda-graphics-plugin-heroicons,
xuda-graphics-plugin-phosphor-icons, xuda-graphics-plugin-tabler-icons,
xuda-graphics-plugin-iconoir.
A library plugin exposes methods: named, typed, callable actions with fields
and a response. Events and workflows invoke them like builtin steps. Examples:
xuda-library-plugin-firebase-auth,
xuda-library-plugin-firebase-push-notification,
xuda-library-plugin-twilio, xuda-library-plugin-stripe-core,
xuda-library-plugin-chatgdp,
xuda-library-plugin-studio-ai-app-builder.
A ui plugin decorates an existing DOM element with reactive behavior. It exports
properties (studio-time schema) and a fn(plugin_name, el, properties) runtime.
Examples: xuda-ui-plugin-moment, xuda-ui-plugin-flatpickr,
xuda-ui-plugin-sortable, xuda-ui-plugin-tippy,
xuda-ui-plugin-libphonenumber.
A widget plugin owns the container: it replaces or fills a mounted element with its own
DOM. It exports capabilities, methods, and a per-method runtime function.
Examples: xuda-widget-plugin-chartjs,
xuda-widget-plugin-ag-grid, xuda-widget-plugin-full-calendar,
xuda-widget-plugin-avatar, xuda-widget-plugin-google-maps.
Every Xuda plugin, regardless of category, follows the same physical layout. The names below are the ones the loader looks for when it reads your plugin from disk or the marketplace.
A regular npm manifest. Must use the prefix @xuda.io/xuda-<category>-plugin-<name>.
Should include an svg_icon field (the marketplace tile icon) and any
runtime dependencies under devDependencies (they are bundled).
The studio-time schema. Exports capabilities, methods,
properties, and/or tabs, depending on category. The studio reads this
to generate forms, option lists, and menus. No runtime behavior here.
The browser entry point. Exports a function per method (_default,
create_account, etc.) or a single fn for UI plugins.
This is what actually runs when the plugin is invoked.
The Node-side entry. Database plugins implement read / upsert
/ delete here. Library plugins that need privileged calls
(billing, email, AI, push) run there. Skipped for pure UI and widget plugins.
Human documentation. The marketplace renders it in the plugin detail page. Keep features, installation, properties table, and a short example.
The bundler contract. Entry is src/runtime.mjs, output is dist/ as
a single ES module with a named export map. The runtime imports
dist/runtime.mjs in production and falls back to runtime.mjs in draft
mode via the studio.
package.json{
"name": "@xuda.io/xuda-widget-plugin-hello",
"version": "1.0.0",
"description": "Minimal Xuda widget plugin",
"scripts": {
"pub": "npm version patch --force && npm publish --access public",
"dev": "vite",
"build": "vite build"
},
"svg_icon": "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'>...</svg>",
"author": "xuda.io",
"license": "Proprietary software licenses",
"devDependencies": {
"vite": "^5.4.10"
}
}
When a page uses a plugin-backed node, the browser runtime finds the plugin entry on the project, checks
the capabilities for the current environment, fetches the per-app setup, imports
runtime.mjs, and invokes the target method with the resolved field values plus a
params object.
xuda_runtime.browser.widgets.js)// 1. Resolve plugin metadata from the project
const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
const definition = await func.runtime.widgets.get_definition(widget_context);
// 2. Check capabilities for the current environment
if (!func.runtime.widgets.supports_current_environment(definition)) {
return report_error(`plugin ${plugin_name} is not available in the current environment`, true);
}
// 3. Resolve typed field values for the selected method
const fields_ret = await func.runtime.widgets.get_fields_data(
widget_context, definition.methods[method].fields, propsP,
);
// 4. Fetch the per-app setup (connection strings, API keys, ...)
const plugin_setup = await func.utils.get_plugin_setup(SESSION_ID, plugin_name);
// 5. Import the bundled runtime module
const fx = await func.runtime.widgets.get_resource(widget_context, 'runtime.mjs');
// 6. Invoke the method
await fx[method](fields, params);
Most supply-chain risk in modern apps comes from the breadth of what a dependency is allowed to do:
a regular npm package can run install scripts, monkey-patch globals, pull in transitive modules,
read process.env, and reach into the file system the moment it is required.
A Xuda plugin cannot do any of that. The loader imports a fixed set of named files
(index.mjs, runtime.mjs, optionally server.mjs) and reads
a fixed set of named exports. Anything else in the package is dead weight to the runtime.
That narrowness is what makes plugin authoring feel quick: there is very little surface to learn
and even less surface to get wrong.
The loader never executes preinstall, postinstall, or any npm lifecycle hook.
Plugins are static assets: a schema file, a runtime file, and an optional server file. There is no
place for a package to run code during adoption, so the whole class of
malicious-install supply-chain attacks is closed at the door.
A widget plugin only touches its container_node. A UI plugin only touches the element
handed to it. A database plugin runs in a scoped server module with the table and session it was
called for. A misbehaving chart renderer cannot corrupt the auth flow; a buggy CouchDB adapter cannot
see another plugin's Stripe key. The runtime passes in exactly what the method needs and nothing more.
Before your method runs, func.runtime.widgets.get_fields_data coerces every field value
against its declared type. Strings stay strings; numbers are parsed; booleans are normalized; arrays
and objects are JSON.parsed. Mandatory fields are enforced at studio time. The plugin
body never sees raw, unsanitized form values, so plugin authors cannot accidentally propagate bad
input into DOM strings, SQL fragments, or shell calls.
Per-app setup (API keys, connection strings, tenant IDs) is fetched through
func.utils.get_plugin_setup(SESSION_ID, plugin_name) and keyed by plugin name.
Plugin A cannot read Plugin B's setup. The cache is namespaced, the fetch is namespaced, and the
payload is handed back only to the specific method that asked. Database adapters can also ship a
public_filter(doc) export to strip secrets from any config that ever crosses to the
browser.
capabilities = { browser, headless } is a hard check, not a hint.
supports_current_environment rejects plugins that try to run where they do not belong,
so a library that holds server-side credentials cannot be tricked into executing in the browser,
and a browser-only widget cannot be bundled into an SSR path where it would leak sensitive DOM
globals.
Plugins are referenced per app. Adopting a new version of a plugin is an explicit action in the
studio, not a silent npm update. Combined with the scoped @xuda.io/
namespace and the marketplace review pipeline, this makes it straightforward to audit exactly which
plugin builds are live in production across every app you run.
package.json#main that the loader honors, no transitive dependency graph pulled in at
runtime, no global prototype to pollute. The AI writes five named exports and one runtime function;
the platform enforces everything else. A human reviewer then audits five named exports and one runtime
function, not an open-ended npm package. Fast to generate, fast to review, and structurally safe by
construction.
npm install.
process.env, filesystem, and globals.
^, ~).
runtime.mjs / index.mjs / server.mjs are ever imported.
fields and params. That is the surface.
The same five steps apply to every plugin, regardless of category. Use the buttons to step through the stages and see what happens at each point.
Stage
Author the schema and the runtime. Declare categories, methods, fields, capabilities. Ship a small, versioned, scoped npm package. The rest is the platform.