JavaScript | Patchies
Loading Patchies...

JavaScript

Patchies provides a JavaScript API available in all JS-enabled objects — from js and worker to visual objects like p5, canvas, hydra, and three. Use it to send and receive messages, run timers, react to audio, and add custom logic to any part of your patch.

Try the starter patch to see message passing in action.

Supported Objects

These objects all run JavaScript and share the same API described on this page:

js, worker, p5, canvas, canvas.dom, textmode, textmode.dom, three, three.dom, hydra, regl, swgl, dom, vue, sonic~, tone~, elem~

Expression objects like filter, map, tap, and scan evaluate code once per incoming message and cannot use messaging or timer functions.

Your First JS Object

Create a js object (press Enter and type js). You'll see a code editor. Try this:

// Receive a message and send back its double
recv((data) => {
  send(data * 2);
});

Connect a slider to the inlet, and a peek to the outlet. Drag the slider — the doubled value appears in peek.

Tip: Use console.log() to print values to the virtual console (open it with the console button in the toolbar). It shows output from your JS objects, separate from the browser console.

Essentials

Logging

console.log("Hello!");
console.log("The value is:", 42);

Output appears in the virtual console inside Patchies, not the browser's DevTools console.

Sending & Receiving Messages

Use send() to output a value from your object, and recv() to listen for incoming messages:

// Send a message out of the default outlet
send({ type: "bang" });
send(42);
send("hello world");

// Receive messages from connected inlets
recv((data) => {
  console.log("Got:", data);
  send(data); // forward it
});

You can check which inlet a message came from using meta.inlet:

recv((data, meta) => {
  if (meta.inlet === 0) {
    console.log("From inlet 0:", data);
  } else {
    console.log("From inlet 1:", data);
  }
});

Multiple Inlets & Outlets

Use setPortCount(inletCount, outletCount) to add inlets and outlets:

setPortCount(2, 1); // 2 inlets, 1 outlet

recv((data, meta) => {
  // Route the message out a specific outlet
  send(data, { to: meta.inlet });
});

Sending to a Specific Outlet

send("first",  { to: 0 }); // outlet 0
send("second", { to: 1 }); // outlet 1

Named Channels (Wireless Messaging)

Connect objects without drawing cables by using named channels. This is handy for sending data across a large patch.

// Send to a named channel from one object
send({ x: 100, y: 200 }, { to: 'position' });

// Receive from that channel in another object
recv((data, meta) => {
  console.log(data);           // { x: 100, y: 200 }
  console.log(meta.channel);   // 'position'
  console.log(meta.source);    // ID of the sender object
}, { from: 'position' });

The to option handles both cables and channels:

send(data, { to: 0 });          // outlet 0 (cable)
send(data, { to: 'position' }); // named channel (wireless)

Named channels work between js, worker, and the visual send/recv objects.

Timers

All timers clean up automatically when you edit the code or remove the object — no memory leaks:

// Run every 500ms
setInterval(() => {
  send({ type: "bang" });
}, 500);

// Run once after 1 second
setTimeout(() => {
  send("done!");
}, 1000);

// Wait before continuing (works with top-level await)
await delay(2000);
send("2 seconds later");

// Run on every animation frame
requestAnimationFrame(() => {
  // great for smooth visual updates
});

Important: Always use the Patchies versions (setInterval, setTimeout, requestAnimationFrame) — not window.setInterval etc. The Patchies versions clean up automatically; the window versions will keep running even after you change your code.

Top-Level Await

You can use await directly at the top level of your script:

await delay(1000);
send("started after 1 second");

This is useful for sequencing things or waiting for data before starting.

Custom Cleanup

Register code that runs when your object is removed or the code is re-executed:

const socket = new WebSocket("wss://example.com");

onCleanup(() => {
  socket.close();
  console.log("Cleaned up!");
});

Display Title

Change the label shown on the object in the patch:

setTitle("counter: 0");

let count = 0;
setInterval(() => {
  count++;
  setTitle(`counter: ${count}`);
}, 1000);

Tip: For VFS, storage, audio reactivity, clock, AI, and presentation APIs, see JS Integrations.

See Also