# How to Specify a Custom Browser Executable Path in Zendriver

> Specify a custom browser executable path in Zendriver by passing browser_executable_path to zd.start() or Config(). Easily point to any Chromium binary, overriding auto-discovery.

- Repository: [CDP Driver/zendriver](https://github.com/cdpdriver/zendriver)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Pass the `browser_executable_path` parameter to `zd.start()`, `Config()`, or `Browser.create()` to override Zendriver's auto-discovery mechanism and point to any Chromium-based binary.**

Zendriver is a Python library for controlling Chromium-based browsers through the Chrome DevTools Protocol. When you need to run a portable browser build, a custom-compiled version, or an installation located outside standard system directories, you must specify a custom browser executable path to ensure Zendriver launches the correct binary.

## Why Specify a Custom Browser Executable Path?

By default, Zendriver automatically discovers browser binaries by scanning common installation directories and the system `PATH`. However, auto-discovery fails in several scenarios:

- **Portable builds** stored in user directories or on external drives
- **Custom-compiled Chromium** versions for specific testing requirements
- **Enterprise environments** where browsers are installed in non-standard locations
- **Multiple browser versions** side-by-side for compatibility testing

In these cases, explicitly setting the `browser_executable_path` parameter ensures Zendriver uses your intended binary.

## How Zendriver Locates the Browser Binary

Understanding the internal flow helps debug path-related issues. Zendriver processes the executable path through three distinct layers:

### The Configuration Layer

The `Config` class in [`zendriver/core/config.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/config.py) (lines 66‑70) accepts `browser_executable_path` during instantiation. If omitted, the constructor calls `find_executable()` to locate the binary automatically.

```python
from zendriver import Config

# Explicit path stored in Config instance

config = Config(browser_executable_path="/opt/chrome/chrome")

```

### The Browser Creation Layer

`Browser.create()` in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) (lines 75‑78) forwards the supplied `browser_executable_path` to the `Config` object. This method serves as the primary entry point for browser instantiation.

```python
from zendriver import Browser

browser = await Browser.create(
    browser_executable_path="/usr/local/bin/brave"
)

```

### Runtime Validation

When `Browser.start()` executes, Zendriver validates that the file exists at the specified path. If validation fails, the method raises an informative error indicating the file cannot be found. This check occurs in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) (lines 336‑350).

## Methods to Specify a Custom Browser Executable Path

Zendriver provides three APIs for passing the executable path, ranging from high-level helpers to low-level class methods.

### Method 1: Using the High-Level `zd.start()` Helper

The simplest approach uses the `zd.start()` convenience function. Pass `browser_executable_path` as a keyword argument:

```python
import asyncio
import zendriver as zd

async def main():
    browser = await zd.start(
        headless=False,
        browser_executable_path="/opt/custom-chrome/chrome",
        browser="chrome",  # optional: force Chrome instead of auto-detect

    )
    page = await browser.get("https://example.com")
    await page.save_screenshot()
    await browser.stop()

asyncio.run(main())

```

The helper forwards the argument to `Browser.create`, which constructs the underlying `Config` object.

### Method 2: Creating a Reusable Config Object

For scenarios requiring multiple browser instances with the same settings, instantiate `Config` directly:

```python
import asyncio
import zendriver as zd
from zendriver import Config

async def main():
    cfg = Config(
        headless=False,
        browser_executable_path="C:\\Users\\Me\\Portable\\Brave\\brave.exe",
        browser="brave",  # explicitly select Brave

    )

    # First browser instance

    b1 = await zd.start(config=cfg)
    await b1.get("https://github.com")
    await b1.stop()

    # Second browser instance reuses the same binary

    b2 = await zd.start(config=cfg)
    await b2.get("https://python.org")
    await b2.stop()

asyncio.run(main())

```

Creating a `Config` once avoids repeated path resolution and ensures consistent settings across instances.

### Method 3: Direct Browser.create API

For maximum control, use the low-level `Browser.create` class method:

```python
import asyncio
from zendriver import Browser

async def main():
    browser = await Browser.create(
        headless=False,
        browser_executable_path="/usr/local/bin/custom-chrome",
        browser="chrome",
    )
    page = await browser.get("https://docs.python.org/3/")
    await page.save_screenshot()
    await browser.stop()

asyncio.run(main())

```

This approach exposes all `Browser.create` parameters, including `user_data_dir`, `sandbox`, and `lang`.

## Understanding the Fallback Mechanism

When `browser_executable_path` is `None` (the default), Zendriver invokes `find_executable()` defined in [`zendriver/core/config.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/config.py) (lines 311‑393). This function builds a list of candidate binaries based on the requested `browser` parameter (`"chrome"`, `"brave"`, or `"auto"`).

The function probes:
- The system `PATH` environment variable
- Known installation directories on Windows (`Program Files`, `LocalAppData`)
- macOS bundle locations (`/Applications`, `~/Applications`)

It returns the shortest existing path. If no binary is found, it raises a `FileNotFoundError` with a clear message suggesting the use of `browser_executable_path`.

## Summary

- **Override auto-discovery** by passing `browser_executable_path` to any Zendriver entry point.
- **Three API levels** support custom paths: `zd.start()` for convenience, `Config()` for reusability, and `Browser.create()` for full control.
- **Validation occurs** in `Browser.start()` at runtime, raising informative errors if the binary is missing.
- **Fallback logic** in `find_executable()` searches standard directories only when no custom path is provided.

## Frequently Asked Questions

### What happens if the specified browser executable path does not exist?

Zendriver validates the executable path during `Browser.start()` in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) (lines 336‑350). If the file does not exist or is not accessible, the library raises a `FileNotFoundError` with a descriptive message indicating the invalid path, allowing you to correct the configuration before retrying.

### Can I use a portable version of Chrome with Zendriver?

Yes. Portable browser builds are a primary use case for the `browser_executable_path` parameter. Simply provide the absolute path to the portable binary (e.g., `C:\Tools\ChromePortable\chrome.exe` or `/mnt/usb/chrome-linux/chrome`) when calling `zd.start()` or creating a `Config` instance.

### How does Zendriver choose between Chrome and Brave if I don't specify a browser?

When `browser_executable_path` is omitted and the `browser` parameter is set to `"auto"` (the default), Zendriver calls `find_executable()` in [`zendriver/core/config.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/config.py) (lines 311‑393). This function searches for both Chrome and Brave binaries in standard installation directories and returns the first valid path found. If neither is found, it raises a `FileNotFoundError`.

### Is it possible to specify the executable path via environment variables?

Zendriver does not natively check environment variables like `BROWSER_EXECUTABLE_PATH`. However, you can easily implement this pattern in your Python code by reading from `os.environ` and passing the value to the `browser_executable_path` parameter. This approach keeps your configuration external while maintaining explicit control over the binary selection.