How to Set a Custom User Agent String with Zendriver: 2 Methods Explained
Zendriver provides two ways to set a custom user agent string: pass the user_agent parameter to Browser.create() at launch for a persistent global setting, or call Tab.set_user_agent() on a running tab to override it via Chrome DevTools Protocol (CDP) for that specific target.
Zendriver is a Python library for browser automation built on the Chrome DevTools Protocol. Controlling the HTTP User-Agent header is essential for web scraping, testing mobile responsiveness, or bypassing bot detection. This guide explains how to set a custom user agent string with Zendriver using both launch-time configuration and runtime overrides.
Method 1: Set User Agent at Browser Launch (Recommended)
The most reliable way to set a custom user agent is to pass it when creating the browser instance. This ensures the custom string is applied to all tabs and network requests from the start.
In zendriver/core/browser.py, the Browser.create() method (lines 74-92) accepts a user_agent parameter and passes it to a Config instance. The Config class in zendriver/core/config.py stores this value (lines 48-71) and converts it into a --user-agent= CLI flag in the __call__ method (lines 13-15). Chrome processes this flag at launch, embedding the custom string into every request and the navigator.userAgent JavaScript property.
import asyncio
import zendriver as zd
async def main() -> None:
# Pass a custom user-agent string to the start helper
browser = await zd.start(user_agent="MyApp/1.0 (+https://example.com)")
tab = browser.main_tab
# Verify that the page sees the custom string
ua = await tab.evaluate("navigator.userAgent")
print("navigator.userAgent →", ua) # → MyApp/1.0 (+https://example.com)
await browser.stop()
if __name__ == "__main__":
asyncio.run(main())
This example follows the pattern shown in examples/set_user_agent.py within the repository.
Method 2: Override User Agent on a Running Tab
If the browser is already running, you can override the user agent for a specific tab using the Chrome DevTools Protocol. This affects only the current tab and subsequent network requests from it.
In zendriver/core/tab.py, the Tab.set_user_agent() method (lines 1667-1704) invokes cdp.network.set_user_agent_override() to update the browser's network stack for that target. It can also update navigator.userAgent via an evaluation fallback if needed.
import asyncio
import zendriver as zd
async def main() -> None:
browser = await zd.start()
tab = browser.main_tab
# Override only for this tab
await tab.set_user_agent(
"AnotherAgent/2.5",
accept_language="fr-FR,fr;q=0.9",
platform="Linux x86_64"
)
# Check the new values
print(await tab.evaluate("navigator.userAgent")) # → AnotherAgent/2.5
print(await tab.evaluate("navigator.language")) # → fr-FR,fr;q=0.9
print(await tab.evaluate("navigator.platform")) # → Linux x86_64
await browser.stop()
if __name__ == "__main__":
asyncio.run(main())
How the User Agent Override Works Under the Hood
Understanding the implementation details helps you choose the right approach for your use case.
Launch-Time Configuration
When you pass user_agent to Browser.create(), the Config class in zendriver/core/config.py appends the value as a --user-agent= command-line argument. Chrome reads this flag during startup, setting the user agent globally for the entire browser process. This method is the most robust because it requires no additional network calls after launch.
Runtime CDP Override
The Tab.set_user_agent() method uses the Chrome DevTools Protocol command Network.setUserAgentOverride. This command instructs Chrome to replace the user agent string for all subsequent HTTP requests from the specified target. According to the source in zendriver/core/tab.py, this override is target-specific, meaning other tabs continue using the default or globally configured user agent.
Headless Mode Handling
When running Chrome in headless mode, the default user agent typically includes the substring "HeadlessChrome", which many bot detection systems flag immediately. In zendriver/core/connection.py, the Connection._prepare_headless() method (lines 46-48) automatically strips the "Headless" suffix from the user agent before applying the override, helping you avoid detection when using headless browsers.
Summary
- Pass
user_agenttoBrowser.create()(orzd.start()) to set a persistent custom user agent for the entire browser session via the--user-agentCLI flag. - Call
Tab.set_user_agent()on a running tab to override the user agent dynamically for that specific target using CDP commands. - Headless mode automatically sanitizes the user agent string to remove the "Headless" identifier, reducing detection risk.
- Verify your configuration by evaluating
navigator.userAgentin the browser to confirm the override took effect.
Frequently Asked Questions
Can I change the user agent after the browser has started?
Yes. While the launch-time method locks the user agent for the entire process, you can override it on specific tabs after startup by calling Tab.set_user_agent(). This method uses the Chrome DevTools Protocol to update the network stack for that target without restarting the browser.
Does setting a custom user agent affect all tabs or just one?
It depends on the method you choose. Passing user_agent to Browser.create() applies the string globally to every tab and request in the browser instance. Conversely, Tab.set_user_agent() affects only the specific tab instance you call it on, leaving other tabs with their original user agent.
How do I check what user agent the browser is currently using?
Evaluate the navigator.userAgent property in the browser context. In Zendriver, use await tab.evaluate("navigator.userAgent") to retrieve the current string. This confirms whether your custom override was applied successfully and helps debug detection issues.
Will the headless mode user agent contain "HeadlessChrome"?
Not if you use Zendriver's built-in handling. The Connection._prepare_headless() method in zendriver/core/connection.py automatically strips the "Headless" suffix from the user agent string when running in headless mode, helping you avoid common bot detection filters that scan for this substring.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →