Skip to main content

Overview

JavaScript offers the fastest path to building Noorle plugins. Using ComponentizeJS, you can compile standard JavaScript directly to WebAssembly components without a separate build toolchain.
JavaScript plugins share the same ComponentizeJS toolchain as TypeScript. If you prefer type safety, see the TypeScript guide.

Prerequisites

Quick Start

1
Install ComponentizeJS
2
npm install -g @bytecodealliance/componentize-js @bytecodealliance/jco
3
Create your project
4
mkdir my-plugin && cd my-plugin
npm init -y
5
Define the WIT interface
6
Create world.wit:
7
package noorle:plugin;

world plugin {
  export tool: interface {
    call: func(name: string, input: string) -> string;
    list: func() -> string;
  }
}
8
Write your plugin
9
Create plugin.js:
10
export const tool = {
  list() {
    return JSON.stringify([
      {
        name: "hello",
        description: "Returns a greeting",
        inputSchema: {
          type: "object",
          properties: {
            name: { type: "string", description: "Name to greet" }
          },
          required: ["name"]
        }
      }
    ]);
  },

  call(name, input) {
    const args = JSON.parse(input);

    switch (name) {
      case "hello":
        return JSON.stringify({
          content: [{ type: "text", text: `Hello, ${args.name}!` }]
        });
      default:
        return JSON.stringify({
          error: `Unknown tool: ${name}`
        });
    }
  }
};
11
Build the WASM component
12
jco componentize plugin.js --wit world.wit -o plugin.wasm
13
Add configuration
14
Create noorle.yaml:
15
schema_version: "1.0"
metadata:
  name: my-js-plugin
  description: A simple greeting plugin
  version: 0.1.0
16
Package and upload
17
tar czf my-plugin.npack plugin.wasm noorle.yaml
noorle plugin push my-plugin.npack

Working with External Data

JavaScript plugins can process and transform data:
export const tool = {
  list() {
    return JSON.stringify([
      {
        name: "parse_csv",
        description: "Parse CSV text into structured JSON",
        inputSchema: {
          type: "object",
          properties: {
            csv: { type: "string", description: "CSV content" },
            hasHeaders: { type: "boolean", description: "Whether first row is headers" }
          },
          required: ["csv"]
        }
      }
    ]);
  },

  call(name, input) {
    const args = JSON.parse(input);

    if (name === "parse_csv") {
      const lines = args.csv.trim().split("\n");
      const hasHeaders = args.hasHeaders !== false;
      const headers = hasHeaders
        ? lines[0].split(",").map(h => h.trim())
        : lines[0].split(",").map((_, i) => `col_${i}`);
      const dataLines = hasHeaders ? lines.slice(1) : lines;

      const rows = dataLines.map(line => {
        const values = line.split(",").map(v => v.trim());
        const row = {};
        headers.forEach((h, i) => { row[h] = values[i] || ""; });
        return row;
      });

      return JSON.stringify({
        content: [{ type: "text", text: JSON.stringify(rows, null, 2) }]
      });
    }

    return JSON.stringify({ error: `Unknown tool: ${name}` });
  }
};

Error Handling

Return structured errors so agents can understand what went wrong:
call(name, input) {
  try {
    const args = JSON.parse(input);
    // ... tool logic
  } catch (err) {
    return JSON.stringify({
      isError: true,
      content: [{
        type: "text",
        text: `Error: ${err.message}`
      }]
    });
  }
}

JavaScript vs TypeScript

AspectJavaScriptTypeScript
SetupSimpler — no compile step for sourceRequires tsc before componentize
Type safetyNone — runtime errors onlyCompile-time type checking
Toolchainjco componentize directlytscjco componentize
Best forQuick prototyping, simple toolsProduction plugins, complex logic
Both use the same ComponentizeJS runtime and produce identical WASM output. The choice is purely about development experience.

Limitations

JavaScript plugins run in a WebAssembly sandbox. The following are not available:
  • fetch() or network APIs (use permissions for network access)
  • fs module (use declared filesystem permissions)
  • setTimeout / setInterval
  • Dynamic import()

Next Steps

Configuration

Full noorle.yaml reference

Permissions

Configure network and filesystem access

Publishing

Upload and version your plugin

TypeScript Guide

Add type safety to your plugin