How to Configure user_data_dir for Persistent Browser Profiles in Zendriver

The user_data_dir parameter in zendriver controls whether Chrome launches with a temporary auto-generated profile or a persistent custom directory, determining if cookies, extensions, and login states survive between browser sessions.

Zendriver provides flexible profile management through the user_data_dir configuration option. This parameter dictates where Chrome stores user data including cookies, local storage, and extensions. Understanding how zendriver handles this directory is essential for automation workflows that require state persistence or complete isolation between browser instances.

Understanding user_data_dir in Zendriver

The profile directory lifecycle is managed cooperatively by two core components: the Config class, which defines the path and persistence behavior, and the Browser class, which executes the cleanup logic. According to the zendriver source code, the implementation splits responsibility between zendriver/core/config.py and zendriver/core/browser.py.

The Config Class

In zendriver/core/config.py, the Config class exposes user_data_dir as a property that lazily initializes temporary storage. When you access config.user_data_dir without previously setting it, the property invokes temp_profile_dir() to generate a unique temporary folder path (typically under /tmp/ with a uc_ prefix).

Setting the attribute manually triggers an internal flag: the setter assigns True to _custom_data_dir. This flag drives the uses_custom_data_dir property, which the browser queries to decide whether the directory is eligible for automatic deletion. If you supplied a custom path, zendriver treats the directory as persistent and preserves it after shutdown.

The Browser Class

In zendriver/core/browser.py, the Browser class consumes the configuration during startup. The start() method appends --user-data-dir={path} to the Chrome launch arguments, directing the browser engine to the specified location. When browser.stop() executes, the internal _cleanup_temporary_profile() method checks config.uses_custom_data_dir. If this returns False (indicating an auto-generated temporary profile), the method recursively deletes the folder. If True, the cleanup routine skips deletion, leaving your custom profile intact on disk.

Configuration Patterns

Zendriver supports four primary patterns for managing browser profiles, each suited to different automation requirements.

Auto-Generated Temporary Profiles

By default, instantiating Config() without arguments creates an ephemeral profile. This pattern ensures complete isolation—every browser session starts with a fresh state, and no disk space accumulates after stop() executes.

import zendriver as zd

cfg = zd.Config()
browser = await zd.start(cfg)
print("Profile location:", browser.config.user_data_dir)

# Output: /tmp/uc_8f3a2b (example path)

await browser.stop()

# Directory /tmp/uc_8f3a2b is automatically removed

Persistent Custom Profiles

Supplying a path to the user_data_dir parameter creates a reusable profile. This is critical for workflows requiring authentication persistence, installed extensions, or specific browser settings across multiple runs.

import zendriver as zd
import os

profile_path = os.path.expanduser("~/.zendriver_profiles/production")
cfg = zd.Config(user_data_dir=profile_path, headless=False)
browser = await zd.start(cfg)

# Navigate and authenticate

await browser.get("https://example.com/login")

# ... perform login actions ...

await browser.stop()

# ~/.zendriver_profiles/production remains on disk with cookies intact

Because the Config constructor receives a explicit path, uses_custom_data_dir evaluates to True, and the cleanup routine preserves your data.

Isolated Profiles for Concurrent Browsers

When launching multiple browser instances from the same base Config without a custom user_data_dir, zendriver guarantees isolation. As confirmed in tests/core/test_multiple_browsers.py, each Browser.create() call receives a distinct temporary directory, preventing profile collision bugs during concurrent execution.

import zendriver as zd

base_cfg = zd.Config()

browser_a = await zd.start(base_cfg)
browser_b = await zd.start(base_cfg)
browser_c = await zd.start(base_cfg)

# Each browser operates in its own isolated environment

print("A:", browser_a.config.user_data_dir)  # /tmp/uc_abc123

print("B:", browser_b.config.user_data_dir)  # /tmp/uc_def456

print("C:", browser_c.config.user_data_dir)  # /tmp/uc_ghi789

await browser_a.stop()
await browser_b.stop()
await browser_c.stop()

Sharing Profiles Across Multiple Runs

To maintain state between distinct Python executions, reuse the same absolute path across separate Config instances. This pattern preserves cookies, local storage, and extension data between automation scripts.

import zendriver as zd

shared_path = "/tmp/shared_automation_profile"

# First execution

cfg1 = zd.Config(user_data_dir=shared_path)
browser1 = await zd.start(cfg1)
await browser1.get("https://example.com")
await browser1.stop()

# Second execution (hours or days later)

cfg2 = zd.Config(user_data_dir=shared_path)
browser2 = await zd.start(cfg2)

# Browser2 retains all cookies and storage from browser1's session

await browser2.stop()

Code Examples

Launching with a Temporary Profile

import zendriver as zd

config = zd.Config()
browser = await zd.start(config)
print("Temporary profile:", browser.config.user_data_dir)

await browser.get("https://example.com")
await browser.stop()  # Auto-cleanup removes the temp directory

Launching with a Persistent Custom Profile

import zendriver as zd

custom_dir = "/home/user/chrome_profiles/persistent"
config = zd.Config(user_data_dir=custom_dir)
browser = await zd.start(config)

# Profile persists after stop()

await browser.stop()

Launching Multiple Isolated Browsers

import zendriver as zd

base = zd.Config()

b1 = await zd.start(base)
b2 = await zd.start(base)

assert b1.config.user_data_dir != b2.config.user_data_dir
print(f"Browser 1: {b1.config.user_data_dir}")
print(f"Browser 2: {b2.config.user_data_dir}")

await b1.stop()
await b2.stop()

Reusing a Profile for Stateful Automation

import zendriver as zd

profile = "/tmp/automation_state"

# Run 1: Establish session

b1 = await zd.start(zd.Config(user_data_dir=profile))
await b1.get("https://example.com/login")
await b1.stop()

# Run 2: Resume with existing cookies

b2 = await zd.start(zd.Config(user_data_dir=profile))
await b2.get("https://example.com/dashboard")  # Still authenticated

await b2.stop()

Summary

  • Auto-generated profiles: Calling Config() without arguments creates a unique temporary directory via temp_profile_dir() in zendriver/core/config.py, cleaned up automatically when browser.stop() executes.
  • Persistent profiles: Providing a path to user_data_dir sets _custom_data_dir = True, which prevents _cleanup_temporary_profile() in zendriver/core/browser.py from deleting the folder.
  • Concurrent isolation: Multiple browsers instantiated from the same default Config receive distinct temporary directories, avoiding profile collisions.
  • State persistence: Reusing the same custom user_data_dir path across separate Config instances preserves cookies, extensions, and storage between browser sessions.

Frequently Asked Questions

What happens if I don't specify a user_data_dir?

Zendriver automatically generates a unique temporary directory under your system's temp folder (typically /tmp/uc_<random_string> on Unix systems). The Config class lazily creates this path when you first access user_data_dir, and the Browser class automatically deletes the folder when you call stop(), ensuring no persistent data remains on disk.

How do I prevent zendriver from deleting my browser profile?

Explicitly set the user_data_dir parameter when constructing your Config object. This action sets the internal _custom_data_dir flag to True, causing uses_custom_data_dir to return True. When browser.stop() triggers _cleanup_temporary_profile(), it checks this flag and skips deletion, leaving your custom directory intact for future sessions.

Can multiple browsers share the same user_data_dir simultaneously?

No. Chrome's architecture locks the user data directory exclusively to a single process. Attempting to launch two Browser instances pointing to the same user_data_dir simultaneously will cause the second launch to fail with a profile lock error. To run concurrent browsers, either use separate custom directories or rely on the default temporary profile behavior, which automatically generates isolated directories for each instance.

Where does zendriver store temporary browser profiles?

The temp_profile_dir() function in zendriver/core/config.py generates paths using Python's tempfile utilities, typically placing them in the system's temporary directory with a uc_ prefix (e.g., /tmp/uc_8f3a2b). These directories exist only for the lifespan of the Browser instance and are removed by _cleanup_temporary_profile() in zendriver/core/browser.py during shutdown, provided no custom user_data_dir was specified.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →