Skip to main content

Overview

Tools are functions that expand what your agent can do beyond basic browser actions. Add custom tools to:
  • Call external APIs
  • Access databases
  • Implement human-in-the-loop workflows
  • Handle 2FA codes
  • Execute custom JavaScript
  • Integrate with Playwright or Selenium
  • Send emails or notifications
Tools are called automatically by the LLM when it determines they’re needed for the task.

Creating Your First Tool

1

Initialize Tools

from browser_use import Tools, ActionResult

tools = Tools()
2

Add a Simple Tool

Use the @tools.action() decorator:
@tools.action('Ask human for help with a question')
async def ask_human(question: str) -> ActionResult:
    answer = input(f'{question} > ')
    return ActionResult(
        extracted_content=f'The human responded with: {answer}'
    )
The description parameter is required - the LLM uses it to decide when to call your tool.
3

Pass Tools to Agent

from browser_use import Agent, ChatBrowserUse

agent = Agent(
    task='Ask the user what their favorite color is',
    llm=ChatBrowserUse(),
    tools=tools,
)

await agent.run()

Tool Response Types

Simple String Return

@tools.action('Get current timestamp')
async def get_timestamp() -> str:
    from datetime import datetime
    return datetime.now().isoformat()

ActionResult (Advanced)

from browser_use import ActionResult

@tools.action('Validate email address')
async def validate_email(email: str) -> ActionResult:
    is_valid = '@' in email and '.' in email.split('@')[1]
    
    if is_valid:
        return ActionResult(
            extracted_content=f"Email {email} is valid",
            success=True,
        )
    else:
        return ActionResult(
            error=f"Email {email} is invalid",
            success=False,
        )
ActionResult Fields:
  • extracted_content: Main result shown to agent
  • long_term_memory: Info to remember across steps
  • error: Error message
  • is_done: Mark task as complete
  • success: Whether action succeeded
  • attachments: List of file paths

Accessing Browser State

Critical: Use parameter name browser_session with type BrowserSession (NOT browser: Browser). The agent injects parameters by name matching.
from browser_use import Tools, ActionResult, BrowserSession

tools = Tools()

@tools.action('Get current page title')
async def get_page_title(browser_session: BrowserSession) -> ActionResult:
    # Access current page via CDP
    cdp_session = await browser_session.get_or_create_cdp_session()
    result = await cdp_session.cdp_client.send.Runtime.evaluate(
        params={'expression': 'document.title', 'returnByValue': True},
        session_id=cdp_session.session_id
    )
    title = result.get('result', {}).get('value', '')
    
    return ActionResult(
        extracted_content=f'Page title: {title}'
    )

Real-World Examples

Human-in-the-Loop Approval

@tools.action('Get approval from human before proceeding')
async def get_approval(action_description: str) -> ActionResult:
    print(f"\n⚠️  Agent wants to: {action_description}")
    response = input("Approve? (yes/no) > ").strip().lower()
    
    if response == 'yes':
        return ActionResult(extracted_content="Approved by human")
    else:
        return ActionResult(
            error="Action rejected by human",
            is_done=True  # Stop agent execution
        )

agent = Agent(
    task="Research competitor pricing and update our database",
    llm=ChatBrowserUse(),
    tools=tools,
)

2FA Code Generation

import pyotp

secret_key = "JBSWY3DPEHPK3PXP"  # Your TOTP secret

@tools.action('Generate 2FA authentication code')
async def generate_2fa_code() -> ActionResult:
    totp = pyotp.TOTP(secret_key)
    code = totp.now()
    
    return ActionResult(
        extracted_content=f"2FA code: {code}"
    )

# Use with sensitive data
agent = Agent(
    task="""
    1. Go to example.com/login
    2. Enter username and password
    3. When prompted for 2FA, use generate_2fa_code action
    """,
    llm=ChatBrowserUse(),
    tools=tools,
    sensitive_data={'username': 'myuser', 'password': 'mypass'},
)
See the Authentication guide for more 2FA patterns.

API Integration

import httpx

@tools.action('Search internal database for customer info')
async def search_customer(email: str) -> ActionResult:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.yourcompany.com/customers",
            params={'email': email},
            headers={'Authorization': 'Bearer YOUR_TOKEN'}
        )
        
        if response.status_code == 200:
            data = response.json()
            return ActionResult(
                extracted_content=f"Customer found: {data}"
            )
        else:
            return ActionResult(
                error=f"Customer not found: {email}"
            )

Custom JavaScript Execution

@tools.action('Execute custom JavaScript on the page')
async def execute_js(javascript_code: str, browser_session: BrowserSession) -> ActionResult:
    cdp_session = await browser_session.get_or_create_cdp_session()
    
    result = await cdp_session.cdp_client.send.Runtime.evaluate(
        params={
            'expression': javascript_code,
            'returnByValue': True,
            'awaitPromise': True
        },
        session_id=cdp_session.session_id
    )
    
    if result.get('exceptionDetails'):
        error = result['exceptionDetails'].get('text', 'Unknown error')
        return ActionResult(error=f"JavaScript error: {error}")
    
    value = result.get('result', {}).get('value')
    return ActionResult(extracted_content=f"Result: {value}")

Database Operations

import asyncpg

@tools.action('Save extracted data to database')
async def save_to_database(table: str, data: dict) -> ActionResult:
    conn = await asyncpg.connect(
        user='user', password='password',
        database='mydb', host='localhost'
    )
    
    try:
        columns = ', '.join(data.keys())
        values = ', '.join([f"${i+1}" for i in range(len(data))])
        query = f"INSERT INTO {table} ({columns}) VALUES ({values})"
        
        await conn.execute(query, *data.values())
        return ActionResult(
            extracted_content=f"Saved {len(data)} fields to {table}"
        )
    
    finally:
        await conn.close()

Domain Filtering

Restrict tools to specific domains for safety:
@tools.action(
    'Trigger disco mode animation',
    allowed_domains=['google.com', '*.google.com']
)
async def disco_mode(browser_session: BrowserSession) -> ActionResult:
    cdp_session = await browser_session.get_or_create_cdp_session()
    
    await cdp_session.cdp_client.send.Runtime.evaluate(
        params={
            'expression': """
            (() => {
                document.styleSheets[0].insertRule(
                    '@keyframes wiggle { 0% { transform: rotate(0deg); } '
                    '50% { transform: rotate(10deg); } '
                    '100% { transform: rotate(0deg); } }'
                );
                document.querySelectorAll("*").forEach(el => {
                    el.style.animation = "wiggle 0.5s infinite";
                });
            })()
            """
        },
        session_id=cdp_session.session_id
    )
    
    return ActionResult(extracted_content="Disco mode activated!")
Domain Pattern Formats:
  • 'example.com' - Only https://example.com/*
  • '*.example.com' - All subdomains
  • 'http*://example.com' - Both HTTP and HTTPS
  • 'chrome-extension://*' - Chrome extensions
Wildcards in TLDs (e.g., example.*) are not allowed for security.

Removing Default Tools

Exclude built-in tools you don’t need:
tools = Tools(exclude_actions=['search', 'wait', 'screenshot'])

agent = Agent(
    task="Your task",
    llm=ChatBrowserUse(),
    tools=tools,
)
See Available Tools for the full list.

Tool Parameter Types

The agent automatically fills parameters based on type hints:
from typing import Optional, List
from pydantic import BaseModel

class Product(BaseModel):
    name: str
    price: float
    in_stock: bool

@tools.action('Add products to cart')
async def add_to_cart(
    products: List[Product],
    coupon_code: Optional[str] = None,
    browser_session: BrowserSession = None
) -> ActionResult:
    # products: list of Product objects
    # coupon_code: optional string
    # browser_session: injected by agent
    
    total = sum(p.price for p in products)
    return ActionResult(
        extracted_content=f"Added {len(products)} products, total: ${total}"
    )
Use Pydantic models for complex structured inputs. The LLM will generate valid instances.

Advanced: Actor Integration

Use the Actor for deterministic browser control:
from browser_use import Tools, ActionResult, BrowserSession
from browser_use.actor import Actor

tools = Tools()

@tools.action('Click element by CSS selector')
async def click_by_selector(selector: str, browser_session: BrowserSession) -> ActionResult:
    actor = Actor(browser_session)
    
    # Find element
    elements = await actor.find_elements(selector=selector)
    
    if not elements:
        return ActionResult(error=f"No element found with selector: {selector}")
    
    # Click first match
    await actor.click_element(element=elements[0])
    
    return ActionResult(
        extracted_content=f"Clicked element: {selector}"
    )

Best Practices

1

Clear Descriptions

Write descriptions that tell the LLM when and why to use the tool:Good:
@tools.action('Get approval from human before making purchases or database changes')
Bad:
@tools.action('Get approval')
2

Return Useful Content

Give the agent context about what happened:
return ActionResult(
    extracted_content="Email sent to john@example.com. Message ID: 12345"
)
3

Handle Errors Gracefully

try:
    # Your logic
    return ActionResult(extracted_content="Success")
except Exception as e:
    return ActionResult(error=f"Failed: {str(e)}")
4

Use Type Hints

Help the LLM understand parameter types:
async def my_tool(
    email: str,
    age: int,
    subscribe: bool = False
) -> ActionResult:
    ...

Next Steps

Available Tools

See all built-in browser actions

Actor Reference

Direct browser control methods

Data Extraction

Extract structured data from pages

Production

Deploy with @sandbox decorator