VSG App Backend Setup
Setup · making it real

Wire the web app to your actual ATEM.

The web UI lives at /app/ and runs anywhere — phone, laptop, tablet. To make it actually control your ATEM, you need to run a small Node.js backend on a computer that's on the same Ethernet network as the ATEM. Takes about 10 minutes.

Step 1 · Find your ATEM's IP address

The ATEM lives at a fixed IP on your network.

Open ATEM Software Control on your computer. Top-left dropdown shows the connected ATEM — click the gear icon next to it. Note the "IP Address" field. Write this down — you'll need it in Step 4.

If you've never set the ATEM's network: by default it tries DHCP. Most home routers will assign it something like 192.168.1.50. To make it predictable, set a static IP from ATEM Software Control → Settings → ATEM → Network.

Step 2 · Install Node.js

The backend runs on Node.js v18 or newer.

Download from nodejs.org — pick the LTS version. Works on macOS, Windows, Linux, and Raspberry Pi. After installing, verify in a terminal:

$ node --version
v20.11.0   # or higher
Step 3 · Get the backend code

One file. Two dependencies. No magic.

Create a folder for the backend (anywhere — Desktop, Documents, doesn't matter). Inside it, save these two files:

package.json

{
  "name": "atem-web-bridge",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": { "start": "node server.js" },
  "dependencies": {
    "atem-connection": "^3.5.0",
    "ws": "^8.16.0"
  }
}

server.js

// Bridges a websocket between the VSG web UI and a Blackmagic ATEM
const { Atem } = require('atem-connection');
const { WebSocketServer } = require('ws');

const ATEM_IP = process.env.ATEM_IP || '192.168.1.50';  // <-- your ATEM's IP
const PORT    = parseInt(process.env.PORT || '9001', 10);

const atem = new Atem();
const wss  = new WebSocketServer({ port: PORT });

let latestState = null;

atem.on('connected', () => {
  console.log('[ATEM] connected');
  broadcast({ event: 'info', model: atem.state?.info?.productIdentifier || 'ATEM Mini', firmware: 'connected' });
});
atem.on('error', err => console.error('[ATEM] error:', err));
atem.on('stateChanged', (state, paths) => {
  latestState = state;
  const me = state.video.mixEffects[0];
  if (!me) return;
  broadcast({ event: 'state', state: { program: me.programInput, preview: me.previewInput } });
});

function broadcast(msg) {
  const data = JSON.stringify(msg);
  wss.clients.forEach(c => c.readyState === 1 && c.send(data));
}

wss.on('connection', (ws) => {
  console.log('[WS] client connected');
  if (latestState) {
    const me = latestState.video.mixEffects[0];
    ws.send(JSON.stringify({ event: 'state', state: { program: me.programInput, preview: me.previewInput } }));
  }
  ws.on('message', async (raw) => {
    try {
      const { cmd, payload } = JSON.parse(raw.toString());
      if (cmd === 'cut')        await atem.changeProgramInput(payload.input);
      if (cmd === 'preview')    await atem.changePreviewInput(payload.input);
      if (cmd === 'take' && payload.type === 'cut')  await atem.cut();
      if (cmd === 'take' && payload.type === 'auto') await atem.autoTransition();
    } catch (e) { console.error('[WS] cmd error:', e); }
  });
});

console.log(`[ATEM] connecting to ${ATEM_IP}...`);
console.log(`[WS] listening on ws://0.0.0.0:${PORT}`);
atem.connect(ATEM_IP);
Step 4 · Install & run

Three commands.

  1. Open a terminal in the folder where you saved the two files above.
  2. Install dependencies:
    $ npm install
  3. Start the backend, replacing 192.168.1.50 with your ATEM's actual IP:
    # macOS / Linux:
    $ ATEM_IP=192.168.1.50 npm start
    
    # Windows (PowerShell):
    $ $env:ATEM_IP="192.168.1.50"; npm start

You should see:

[ATEM] connecting to 192.168.1.50...
[WS] listening on ws://0.0.0.0:9001
[ATEM] connected
Step 5 · Connect the web UI

Find this computer's IP, then plug it into the web app.

Find this machine's local IP:

# macOS / Linux:
$ ipconfig getifaddr en0
192.168.1.42

# Windows:
$ ipconfig | findstr IPv4
   IPv4 Address. . . . . . . . . . . : 192.168.1.42

Open the web app at videoswitchguide.com/app/ on any device on the same network. Click the connection status pill in the top-right (or the Connect · Settings button), and enter:

ws://192.168.1.42:9001

Hit Connect. The status pill turns green and starts pulsing. The web UI now controls your real ATEM — from the browser, on any phone, laptop, or tablet on your network.

The connection is local

Your ATEM data never leaves your network. The web UI runs at videoswitchguide.com (over HTTPS), but the control connection is a direct WebSocket to your local machine. Nothing is proxied through this site.

Step 6 · Make it always-on (optional)

Run the backend as a service.

If you want the backend to run automatically without keeping a terminal open:

Troubleshooting

If something doesn't work.

Going further

What this backend doesn't do (yet).

The skeleton above handles cuts and transitions — the most common operations. To extend it for audio mixing, recording control, streaming, keyers, or media graphics, see the full atem-connection library docs. The library exposes essentially every command Blackmagic's official ATEM Software Control sends.

For a more comprehensive solution that handles many devices (including the ATEM Mini), see our deep dive on Bitfocus Companion — an OSS controller that already covers the ATEM exhaustively, and saves you from writing your own backend.