npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@browserless/function

v14.0.0

Published

Execute arbitrary JavaScript code in a secure browser sandbox with access to page DOM and network.

Readme

@browserless/function: Run arbitrary JavaScript inside a browser sandbox.

See the function section on our website for more information.

Install

Using npm:

npm install @browserless/function --save

About

This package provides a secure sandbox for running arbitrary JavaScript code with runtime access to a browser page. It executes user-provided functions in an isolated VM environment, with optional access to Puppeteer's page API.

What this package does

The @browserless/function package allows you to:

  • Execute arbitrary JavaScript in a secure, isolated VM sandbox
  • Access the browser page from within the sandbox for DOM manipulation
  • Capture console output and execution profiling data
  • Pass custom data to the sandboxed function at runtime
  • Handle errors gracefully with structured result objects

Usage

First, create a function instance by calling the factory:

const createFunction = require('@browserless/function')()

You can optionally pass a tmpdir for the sandbox working directory:

const createFunction = require('@browserless/function')({ tmpdir: '/tmp/functions' })

Then use it to run arbitrary JavaScript:

// Simple function without page access
const code = ({ query }) => query.value * 2

const myFn = createFunction(code)
const result = await myFn('https://example.com', { query: { value: 21 } })

console.log(result)
// => { isFulfilled: true, value: 42, profiling: {...}, logging: {...} }

Accessing the page

When your code references page, browserless automatically provides access to the Puppeteer page:

const createFunction = require('@browserless/function')()

// Function with page access
const code = async ({ page }) => {
  const title = await page.title()
  const content = await page.evaluate(() => document.body.innerText)
  return { title, content }
}

const scraper = createFunction(code)
const result = await scraper('https://example.com')

console.log(result)
// => { isFulfilled: true, value: { title: 'Example', content: '...' }, ... }

Available context

The sandboxed function receives these properties:

| Property | Description | |----------|-------------| | url | Target URL passed to the function (no browser required) | | page | Puppeteer Page object (if referenced in code) | | device | Device descriptor with userAgent and viewport | | ...opts | Any custom options passed at runtime |

Result object

The function returns a structured result:

| Property | Description | |----------|-------------| | isFulfilled | true if execution succeeded, false if error | | value | Return value (success) or error object (failure) | | profiling | Execution timing and resource usage (see below) | | logging | Captured console output (log, warn, error, etc.) |

Profiling

The profiling object contains phased timing and resource data:

| Property | Description | |----------|-------------| | phases.compile | Time to compile the sandbox script (ms) | | phases.spawn | Time to spawn the child process (ms) | | phases.run | Time executing the user function (ms) | | phases.total | Total wall-clock time (ms) | | cpu | CPU time consumed (ms) | | memory | Peak memory usage (bytes) |

Teardown

Call .teardown() on the factory instance to clean up sandbox resources:

const createFunction = require('@browserless/function')({ tmpdir: '/tmp/functions' })

// ... use createFunction ...

await createFunction.teardown()

extendPage

Attach extra methods on page. JSON values become async getters. Functions are inlined as async methods (this is the page; use a function, not an arrow, when you need this). If the user function only uses these methods, Chromium is not started:

const myFn = createFunction(({ page }) => page.html(), {
  extendPage: { url, html }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: html, ... }

hostPage

Attach methods on page that the host resolves when the user function calls them, rather than values decided before it runs. Work the function never reaches costs nothing:

const myFn = createFunction(({ page }) => page.content(), {
  hostPage: { content: () => fetchThePage(url) }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: html, ... }, and fetchThePage ran once

fetchThePage runs because the function awaited content. A function returning 420 without touching page never triggers it, and neither does a branch it does not take:

const myFn = createFunction('async ({ page }) => (false ? await page.content() : "skipped")', {
  hostPage: { content: () => fetchThePage(url) }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: 'skipped', ... }, and fetchThePage never ran

Unlike extendPage, the method itself stays on the host rather than being serialized into the isolate; the call travels over the channel at the moment it is made. Its arguments and its result still cross that channel, so both have to be values the channel can carry: a BigInt, for instance, rejects the call rather than resolving it.

Both kinds can sit on the same page, and either counts as satisfying that method, so a function using only these does not start Chromium. A name present on both is the extendPage value, and that host method is not reachable.

The same method and arguments resolve once per run. 32 distinct calls are allowed per run; vmOpts.maxHostCalls changes the cap. The isolate runs untrusted code and can reach the channel directly, so treat every argument as untrusted input. Every hostPage value has to be a function.

Options

const myFn = createFunction(code, {
  // Browserless instance factory
  getBrowserless: () => require('browserless')(),
  
  // Attempts on the `getPage` path, where there is no context to replace and
  // this is the only retry. The default path takes its retry count from the
  // browserless context instead.
  retry: 2,
  
  // Execution timeout in milliseconds
  timeout: 30000,
  
  // Extra methods on `page`. JSON values become async getters; functions
  // are inlined as async methods. If the function only uses these methods,
  // Chromium is not started.
  extendPage: {
    url,
    html
  },

  // Methods on `page` the host resolves when the function calls them. The
  // method stays on the host and one the function never reaches is never
  // resolved, though arguments and results still cross the channel and must
  // be values it can carry. Counts as satisfying that method, like extendPage.
  hostPage: {
    content: () => fetchThePage(url)
  },

  // Options passed to browserless.goto()
  gotoOpts: {
    scripts: ['https://cdn.example.com/library.js'],
    waitUntil: 'networkidle0'
  },
  
  // VM sandbox options (passed to isolated-function)
  vmOpts: { /* ... */ },


  // Run against a page that is already navigated, instead of creating a
  // context and navigating. It replaces that path, so `getBrowserless` is not
  // consulted when it is set. No `goto` happens and the page is never closed:
  // whoever supplied it owns its lifetime. Pass `response` when you have it,
  // so the function still sees `_response`. `timeout` bounds how long the
  // caller waits. It leaves the page open, and the snippet plus its isolate
  // subprocess keep running until the snippet returns or the page is closed.
  // `device` and `response` are optional. Without a device, the `{ userAgent,
  // viewport }` a snippet sees is read off the page, so it matches what the
  // default path reports; pass your own to override it.
  getPage: async () => ({ page, device, response })
})

Reusing a page the caller already loaded, so the target is fetched once. getPage replaces the context-and-navigate path, so getBrowserless is not consulted on this call:

const { page, response } = await somethingThatAlreadyNavigated(url)

const result = await createFunction(code, {
  getPage: async () => ({ page, response })
})(url)

await page.close()

The snippet runs in an isolate connected over browserWSEndpoint, so page.browser() reaches every page on that browser, not only the one it was handed. strictTarget decides which page that is, not what it can reach. This was already true of the navigating path, and it matters more once one context is shared across tasks: treat sharing as a trust boundary this package cannot enforce for you.

timeout rejects the call and leaves the page open. The snippet and its isolate subprocess keep running, so treat the page as busy: leave it alone until that work finishes, or close it. Closing the page ends the snippet.

Retaining a page from browserless requires keepPage, since evaluate closes its page as soon as it resolves:

let page
await context.evaluate(
  currentPage => {
    page = currentPage
    return currentPage.content()
  },
  { keepPage: true }
)(url)

Retaining a page restarts its close watchdog. The new window is the evaluate / withPage timeout, measured from handover. Finish or close the page before that window ends. A later getPage call has its own timeout; the watchdog still closes the page when the evaluate window ends, including while that call is in progress. Set the evaluate timeout long enough to cover the follow-up work.

Examples

Interact with page elements

const createFunction = require('@browserless/function')()

const code = async ({ page }) => {
  await page.waitForSelector('button.submit')
  await page.type('input', '[email protected]', { delay: 200 })
  await page.click('button.submit')
  await page.waitForNavigation()
  return page.title()
}

const clickAndGetTitle = createFunction(code)
const result = await clickAndGetTitle('https://example.com')

Inject external scripts

const createFunction = require('@browserless/function')()

const code = ({ page }) => page.evaluate('jQuery.fn.jquery')

const getjQueryVersion = createFunction(code, {
  gotoOpts: {
    scripts: ['https://code.jquery.com/jquery-3.6.0.min.js']
  }
})

const result = await getjQueryVersion('https://example.com')
// => { isFulfilled: true, value: '3.6.0', ... }

Use npm modules in sandbox

const createFunction = require('@browserless/function')()

const code = async ({ page }) => {
  const _ = require('lodash')
  const text = await page.evaluate(() => document.body.innerText)
  return _.words(text).length
}

const countWords = createFunction(code)
const result = await countWords('https://example.com')

Handle errors

const createFunction = require('@browserless/function')()

const code = () => {
  throw new Error('Something went wrong')
}

const myFn = createFunction(code)
const result = await myFn('https://example.com')

console.log(result.isFulfilled) // => false
console.log(result.value.message) // => 'Something went wrong'

How it fits in the monorepo

This is an extended functionality package. It is not wired into browserless core — install it when you need a sandbox that can touch a page.

Dependencies

| Package | Purpose | |---------|---------| | @browserless/errors | Error normalization and typed errors | | isolated-function | Secure sandboxed execution via child processes | | require-one-of | Auto-detects browserless installation | | acorn / acorn-walk | AST parsing to detect page usage in code |

License

@browserless/function © Microlink, released under the MIT License. Authored and maintained by Microlink with help from contributors.

The logo has been designed by xinh studio.

microlink.io · GitHub microlinkhq · X @microlinkhq