Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Browsers in custom APIs

A custom API can drive a headless Chromium with Playwright or Puppeteer : turn HTML into a PDF, take a screenshot, or read a page. A browser needs system libraries next to its npm package, so it runs in one of two places.

Native process Sandbox
Where the code runs Inside API Maker itself (runOnNativeProcess: true) In the sandbox containers of the account
Where the browser is installed On the server, next to API Maker In the Docker file of the sandbox
Isolation None : a crash of the browser can hurt the server Your code stays apart from API Maker
Set up by The root user of the server An admin, from the admin panel

A browser needs memory : plan 2 GB of RAM and 40 GB of disk for the server, and avoid many browsers at the same time.

Playwright on the native process

  1. Sign in to the server as root (whoami prints root ; sudo -s otherwise) and install Chromium with the libraries it needs :

    npx playwright install --with-deps chromium
    
  2. Add the package to API Maker :

    cd /root/projects/sava_api_maker/
    npm i playwright
    
  3. Create a custom API and set runOnNativeProcess: true in its configuration (the Basic Info section of the custom API).

  4. Use it in the code. This one writes a PDF from HTML into the uploads folder :

Custom API : HTML to PDF with Playwright
import * as T from 'types';
import { join } from 'path';
const playwright = require('playwright');

async function main(g: T.IAMGlobal) {
    const browser = await playwright.chromium.launch();
    const page = await browser.newPage();
    await page.setContent('<p style="font: 18px Arial">Hello from Playwright</p>', { waitUntil: 'load' });
    await page.pdf({
        path: join(__dirname, 'uploads', 'playwright.pdf'),
        format: 'A4',
        printBackground: true,
        margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
    });
    await browser.close();

    // uploads/playwright.pdf is on the server : upload it to a storage provider or send it as a download.
    return 'pdf created on the server';
}

module.exports = main;

Every server, every upgrade

A package installed by hand lives on one server. Put the install command in Root Settings → Deployment Settings → Scripts → Server Startup Script : the script runs when the API Maker process starts, on every server of the cluster, so the package is there on new servers too.

Playwright in the sandbox

The default image of the sandbox has no browser libraries. Two changes in the sandbox settings add them.

  1. Docker file : replace the content with the file below and click Save (top right). It installs Chromium, its system libraries and pnpm.

    FROM node:22-bookworm
    WORKDIR /usr/src/app
    
    RUN npm install -g [email protected]
    RUN apt-get update && apt-get install -y --no-install-recommends build-essential g++ make libc6 python3 python3-dev && rm -rf /var/lib/apt/lists/*
    
    ARG A_DOCKERFILE_HASH
    ENV A_DOCKERFILE_HASH=${A_DOCKERFILE_HASH}
    
    ARG A_PACKAGE_JSON_HASH
    ENV A_PACKAGE_JSON_HASH=${A_PACKAGE_JSON_HASH}
    COPY ./package.json ./
    RUN pnpm install --dangerously-allow-all-builds
    RUN npx -y [email protected] install --with-deps
    
    ARG A_CODE_HASH
    ENV A_CODE_HASH=${A_CODE_HASH}
    
    ARG NODE_OPTIONS
    ENV NODE_OPTIONS=${NODE_OPTIONS}
    
    COPY . .
    
    ARG A_NEW_PACKAGES_INSTALL_CMD
    ENV A_NEW_PACKAGES_INSTALL_CMD=${A_NEW_PACKAGES_INSTALL_CMD}
    RUN $A_NEW_PACKAGES_INSTALL_CMD
    
    EXPOSE 4631
    EXPOSE 4632
    EXPOSE 9229
    CMD [ "npm", "run", "start" ]
    
  2. Dependencies : click Add (+), search for playwright, pick the latest stable version and click Add.

Launch Chromium with --no-sandbox inside the container. This custom API saves a web page as a PDF and sends it as a download :

Custom API : a web page to PDF, in the sandbox
import * as T from 'types';
const fs = require('fs');
const path = require('path');
const { chromium } = require('playwright');

async function main(g: T.IAMGlobal) {
    const outDir = path.join(__dirname, 'uploads');
    if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });

    const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
    const page = await browser.newPage();
    await page.goto('https://playwright.dev', { waitUntil: 'networkidle' });
    await page.pdf({ path: path.join(outDir, 'playwright-dev.pdf'), format: 'A4' });
    await browser.close();

    return {
        __am__downloadFilePath: 'playwright-dev.pdf',
        __am__downloadFolderFileName: 'playwright-dev.pdf',
    };
}

module.exports = main;

Puppeteer on the native process

  1. Sign in to the server as root and install the libraries of Chromium (Ubuntu ; for other systems see the troubleshooting page of Puppeteer) :

    apt update && apt install -y ca-certificates fonts-liberation libatk1.0-0t64 libatk-bridge2.0-0t64 \
      libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 \
      libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 \
      libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
      libxss1 libxtst6 libasound2t64 lsb-release wget xdg-utils
    
  2. Add the package to API Maker :

    cd /root/projects/sava_api_maker/
    npm install puppeteer
    
  3. Set runOnNativeProcess: true in the configuration of the custom API, and use it :

Custom API : HTML to PDF with Puppeteer
import * as T from 'types';
import { join } from 'path';
const puppeteer = require('puppeteer');

async function main(g: T.IAMGlobal) {
    const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
    const page = await browser.newPage();
    await page.setContent('<p>Hello, this paragraph is converted to a PDF using Puppeteer.</p>', { waitUntil: 'load' });
    await page.pdf({ path: join(__dirname, 'uploads', 'puppeteer.pdf'), format: 'A4', printBackground: true });
    await browser.close();

    // uploads/puppeteer.pdf is on the server : upload it to a storage provider or send it as a download.
    return 'ok';
}

module.exports = main;

Good to know

  • Write the files you want to send into the uploads folder and return __am__downloadFilePath : see Download file. Upload them to a storage service instead when they must be kept.
  • Close the browser in every path of the code, errors included : an open browser keeps its memory.
  • A bug on the native process can hurt the whole server : prefer the sandbox when you can, and keep the native process for the APIs which need it.