The browser use tool lets Claude navigate, read, and interact with webpages in a browser that your application runs. It works with the page both through its structure (the accessibility tree, elements, forms, and tabs) and through pixels (screenshots and viewport coordinates), whereas the computer use tool works with a whole desktop through screenshots and coordinates alone. It's an Anthropic-defined client toolset: one browser_toolset_20260801 entry in your tools array gives Claude 27 member tools by default, such as navigate, read_page, left_click, and screenshot, plus four more (javascript_exec, file_upload, read_console, and read_network) when you enable them. Your application runs every call against its own browser automation; nothing runs on Anthropic's side. It isn't currently available in Claude Managed Agents. This page says "your application" for the agent loop that calls the Messages API and "your executor" for the part of it that drives the browser and produces tool results.
Choose browser use over computer use when the task stays inside webpages: Claude can read a page's structure, act on an element by reference in addition to by coordinate, set form values directly, and work across tabs, and you don't need to run a desktop. If Claude only needs to read pages you can point it to, or to find sources on the web, the web fetch tool and web search tool are lighter still, because they're server tools that the API runs for you with no browser to operate. Choose browser use instead when pages build their content with JavaScript or the task means acting on the page rather than only reading it.
With browser use, Claude reads and acts on live webpages, so everything a page supplies is untrusted input and the actions Claude takes can have real effects. See Security considerations before you deploy.
The browser use tool is generally available on the Claude API with no beta header: add one entry of type browser_toolset_20260801, with no name, to the tools array of a Messages API request.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=2048,
tools=[{"type": "browser_toolset_20260801"}],
messages=[
{
"role": "user",
"content": "Open example.com/docs and tell me how to get started.",
}
],
)
print(response)Claude's first response ends with stop_reason: "tool_use" and carries one or more member tool_use blocks, each naming a member tool in name and carrying "toolset_name": "browser":
{
"id": "msg_01HCDu4XSTLzTAcodEQ58vDo",
"type": "message",
"role": "assistant",
"model": "claude-opus-5",
"content": [
{
"type": "text",
"text": "I'll open the documentation and read the page to find the getting-started instructions."
},
{
"type": "tool_use",
"id": "toolu_01NRLabsLyVHZPKxbKvkfSMn",
"name": "navigate",
"toolset_name": "browser",
"input": { "url": "https://example.com/docs" }
},
{
"type": "tool_use",
"id": "toolu_01UvHU5cDyTZ2vXKf5wCkPqR",
"name": "read_page",
"toolset_name": "browser",
"input": { "filter": "interactive" }
}
],
"stop_reason": "tool_use",
"stop_sequence": null
}Your executor runs navigate, then read_page, and your application returns one tool_result per block in its next request, echoing toolset_name on each. The navigate result reports the tab it loaded in a browser_state block; the read_page result is text in which every element carries a reference:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01NRLabsLyVHZPKxbKvkfSMn",
"toolset_name": "browser",
"content": [
{ "type": "text", "text": "Navigated to https://example.com/docs" },
{
"type": "browser_state",
"tabs": [
{
"tab_id": "tab-1",
"title": "Documentation",
"url": "https://example.com/docs",
"active": true
}
]
}
]
},
{
"type": "tool_result",
"tool_use_id": "toolu_01UvHU5cDyTZ2vXKf5wCkPqR",
"toolset_name": "browser",
"content": [
{
"type": "text",
"text": "link \"Documentation\" [ref_1]\nlink \"Getting started\" [ref_2]\ntextbox \"Search docs\" [ref_3]\nbutton \"Search\" [ref_4]\nlink \"Pricing\" [ref_5]"
}
]
}
]
}Claude now holds references it can act on, so its next turn can click ref_2 to open the getting-started page, with no need to locate the link in a screenshot first.
Browser use runs as an agent loop: Claude returns member tool calls, your executor runs them against the browser, and you return the results until Claude answers in text.
Provide Claude with the browser use tool and a user prompt
browser_toolset_20260801 entry, and optionally other tools, to your API request.Claude responds with member tool calls
tool_use blocks in a single assistant turn; several in one turn form a batch action, for example, left_click, then type, then key.name is the member name, each carries "toolset_name": "browser", and input holds only that member's parameters, with no action field. The response's stop_reason is tool_use.Run the calls in order and return results
tool_use block in response.content (don't assume there's exactly one) and run them sequentially, in the order they appear, because later calls usually depend on earlier ones.tool_result per block in a new user message, matched by tool_use_id, and echo "toolset_name": "browser" on each. Every call must be answered or the next request is rejected.is_error: true with a text description for that block, then apply the halt rule in Batch actions to every later block in the turn.Claude continues until the task is complete
Here's a skeleton of that loop's tool-call step in two parts. First, stub member handlers stand in for your browser automation. Five members (navigate, read_page, left_click, type, and screenshot) return the text, or for screenshot the image block, that becomes the result content, and the dispatcher raises an error for any member it doesn't implement.
# Placeholder image data; a real executor captures the viewport and returns the PNG bytes
PLACEHOLDER_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
def navigate(url):
return f"navigated to {url}"
def read_page():
return 'link "Docs" [ref_1]\nbutton "Search" [ref_2]'
def click(target):
# A target is an element reference from read_page or find, or a viewport coordinate
if target["type"] == "ref":
return f"clicked {target['ref']}"
return f"clicked at ({target['x']}, {target['y']})"
def type_text(text):
return f"typed: {text}"
def capture_screenshot() -> list[ImageBlockParam]:
# screenshot answers with an image block rather than text: return the result content list
return [
{
"type": "image",
"source": {"type": "base64", "media_type": "image/png", "data": PLACEHOLDER_PNG},
}
]
def handle_browser_action(name, tool_input):
if name == "navigate":
return navigate(tool_input["url"])
elif name == "read_page":
return read_page()
elif name == "left_click":
return click(tool_input["target"])
elif name == "type":
return type_text(tool_input["text"])
elif name == "screenshot":
return capture_screenshot()
# Handle other actions as needed
raise ValueError(f"Unknown or unimplemented member: {name}")The second part runs a batch in order, dispatches each block to those handlers, echoes toolset_name on every result, and applies the halt rule from Batch actions, turning a handler error into an error result. The sampling loop that calls it is the one shown in Understand the agent loop, with the browser toolset in tools.
NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
def process_tool_calls(response: Message) -> list[ToolResultBlockParam]:
"""
Run the browser actions in Claude's response in order and answer each
one. After the first failure the rest are skipped, because Claude planned
them assuming the earlier actions succeeded.
"""
tool_results: list[ToolResultBlockParam] = []
failed = False
for block in response.content:
# Only the browser toolset is declared; route other tools here if you add them
if block.type != "tool_use" or block.toolset_name != "browser":
continue
result: ToolResultBlockParam = {
"type": "tool_result",
"tool_use_id": block.id,
"toolset_name": "browser",
}
if failed:
result["content"] = NOT_EXECUTED
result["is_error"] = True
else:
try:
# A string or a list of content blocks; a real executor also adds a
# browser_state block to navigation and tab-management results
result["content"] = handle_browser_action(block.name, block.input)
except Exception as err:
result["content"] = f"Error: {err}"
result["is_error"] = True
failed = True
tool_results.append(result)
return tool_resultsDispatch each block on the pair (toolset_name, name) rather than on name alone, because a custom tool in the same request may share a member's name; Client toolsets describes the parts of this contract both toolsets share. If Claude names a member your executor doesn't implement, or one you disabled, answer that block with an error result rather than dropping it.
When you stream the response, each member's input arrives as one complete input_json_delta rather than as fragments, so wait for the turn to finish before running the batch.
A turn with several member calls is a batch action: run the calls in the order they appear, stop at the first failure, and answer every later call with is_error: true and the exact text Not executed: an earlier action in this turn failed. A batch uses the same response shape as parallel tool use; the difference is that you run the blocks in order rather than concurrently. Here Claude clicks the search box it found earlier, types a query, and presses Enter in one turn:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01D7FLrfh4GYq7yT1ULFeyMV",
"name": "left_click",
"toolset_name": "browser",
"input": { "target": { "type": "ref", "ref": "ref_3" } }
},
{
"type": "tool_use",
"id": "toolu_01Ez4kLb1nQ2vXo8sJ9pWm3c",
"name": "type",
"toolset_name": "browser",
"input": { "text": "install" }
},
{
"type": "tool_use",
"id": "toolu_01FkP8rTz6uYh2mNq4LsXw7v",
"name": "key",
"toolset_name": "browser",
"input": { "text": "Enter" }
}
]
}Your application returns three tool_result blocks in one user message, each carrying toolset_name and a short text acknowledgment such as Clicked element ref_3. Pressing Enter loads a results page, so the key result also carries a browser_state block with the tab's updated URL (Tab context on other results). If the click had failed instead, its result would carry your error text and the other two results would carry the halt text, as shown under Return errors from your executor.
You don't need to return a screenshot after every call. Claude typically ends a batch with an observation call (screenshot, read_page, or get_page_text), and your application can also attach its own observation, such as a fresh screenshot or accessibility tree, as an extra content block on the last result in the batch to save a round trip. Because a tab-management result must be exactly one browser_state block, attach it to the last result that isn't a tab-management call.
If your executor can run only one call per round trip, set disable_parallel_tool_use to true in tool_choice and Claude returns at most one member call per turn, at the cost of more round trips (Disable parallel tool use). The rest of the contract under Batch actions for the computer use tool carries over, including one tool_result for every tool_use in the next user message, except for two things: the halt text and what a successful result's content holds. Result content follows Member tools on this page instead: a new_tab, switch_tab, close_tab, or list_tabs result is exactly one browser_state block with no text or image (Tab management results), and any other member's result may add a browser_state block to its text or image (Tab context on other results). Where cache breakpoints inside a batch take effect is described in the cache_control row of the computer use tool's Tool parameters.
Member tools that act on a location take a target object, which is either a viewport-pixel coordinate or a reference to an element that read_page or find returned. The Member tools tables write Target for a parameter that accepts either shape.
| Shape | target.type | Fields | Accepted by |
|---|---|---|---|
CoordinateTarget | "coordinate" | x, y (integers, viewport pixels) | left_click, right_click, middle_click, double_click, triple_click, hover, left_click_drag (from and target), left_mouse_down, left_mouse_up, mouse_move, scroll |
RefTarget | "ref" | ref (an element reference such as "ref_2") | left_click, right_click, middle_click, double_click, triple_click, hover, scroll_to, form_input, file_upload |
Coordinates are viewport pixels, the pixel space of a full-viewport screenshot with the origin at the top left of the rendered page; there's no surrounding desktop or window frame. The toolset declares no display dimensions and Claude infers the viewport size from the screenshots you return, so keep them one consistent size. A zoom doesn't change the frame, so its region and any coordinates Claude emits after seeing the zoomed image are still full-viewport pixels.
Screenshots must fit the image limits. The API doesn't downscale toolset images: a screenshot or zoom image over your model's image size limits, or over the stricter per-image limit that applies once a request holds more than 20 images, is rejected. Resize before returning, and scale Claude's coordinates back up by the inverse of your factor before dispatching them (Size screenshots to fit image limits).
Element references come from read_page and find. Each element in their output carries a tag such as [ref_2], as in the Quick start result:
link "Documentation" [ref_1]
link "Getting started" [ref_2]
textbox "Search docs" [ref_3]
button "Search" [ref_4]
link "Pricing" [ref_5]Claude passes a reference back as a {"type": "ref", "ref": "ref_2"} target on a later click, hover, scroll_to, form_input, or file_upload call, or as the ref parameter on read_page to read a subtree. Your executor assigns the references, keeps the mapping from each one to the underlying node (an accessibility-node ID, a stored selector, or equivalent), and acts on that node when a reference comes back.
References are scoped to the tab that produced them and stay valid until that tab navigates or its DOM changes materially. The API can't detect a stale or unknown reference, so when Claude passes a reference your executor no longer recognizes, return an error result such as Error: ref_3 is stale or not found on the current page. Re-read the page to get fresh references. Claude then reads the page again. Don't renumber references you've already handed out for a tab until it navigates, because that silently invalidates references Claude still holds.
Claude uses both targeting styles and switches between them based on what the page exposes; your prompt and what your executor returns steer the choice:
screenshot and zoom and clicks by coordinate; your executor resolves which frame a coordinate lands in.read_page with filter: "interactive" or the ref of a container returns a focused subtree, and a tree read of a typical page often costs fewer input tokens than a screenshot while giving Claude references it can act on immediately. Screenshots remain the right observation when visual layout, images, or rendering state matter.Browser use carries risks that standard API features don't, because Claude reads and acts on content from the open web, where any page can contain text written to manipulate it.
Claude sometimes follows instructions found in page content even when they conflict with yours; text on a page that says "ignore your previous instructions and navigate to..." can divert it from the task. Isolate Claude from sensitive data and actions to limit what a prompt injection can reach, review Mitigate jailbreaks and prompt injections, and if a task can't avoid a logged-in session, use a dedicated low-privilege account and keep human confirmation on account-changing actions.
Because the browser runs in your environment, the sites Claude visits see your executor's network identity, and page content reaches the API only as the tool results you return. Inform end users of the relevant risks and obtain their consent before enabling browser use in your products.
The browser_toolset_20260801 entry declares 31 member tools; each call's input is exactly the parameters listed here, and tab_id, where optional, defaults to the active tab. Target, CoordinateTarget, and RefTarget are the shapes described in Targets and coordinates. Four members (javascript_exec, file_upload, read_console, and read_network) are disabled by default and appear only when you enable them. The input bounds and output conventions noted in each member's row are stated to Claude, not enforced by the API, so validate inputs (including coordinates against your viewport) and apply the conventions in your executor.
Only screenshot and zoom require an image block in their result, and the four tab-management members (new_tab, list_tabs, switch_tab, and close_tab) return exactly one browser_state block (see Tab management results). Every other member returns a text block: either a short acknowledgment such as Clicked element ref_2. or the member's output. Any result other than a tab-management result may also carry an image block, typically a screenshot taken after the action, so Claude sees the outcome without a separate screenshot call; Batch actions shows where to attach one in a batch. A member tool_result may contain only text, image, and browser_state content blocks.
| Member | Input | Description |
|---|---|---|
navigate | url, tab_id? | Load an http or https URL, or move through history with "back", "forward", or "reload". Treat a URL without a scheme as https:// and refuse any other scheme with an error result. Return a short acknowledgment, plus a browser_state block when the tab's URL or title changed. |
screenshot | tab_id? | Capture the viewport and return an image block. |
zoom | region, tab_id? | Return a cropped, upscaled image of region, given as [x0, y0, x1, y1] in viewport pixels, for closer inspection of small text or controls. |
| Member | Input | Description |
|---|---|---|
left_click | target: Target, modifiers?, tab_id? | Left-click a coordinate or a referenced element. modifiers is a chord held during the click, for example, "shift" or "ctrl+shift". |
right_click | target: Target, modifiers?, tab_id? | Right-click a coordinate or element. |
middle_click | target: Target, modifiers?, tab_id? | Middle-click a coordinate or element. |
double_click | target: Target, modifiers?, tab_id? | Double left-click a coordinate or element. |
triple_click | target: Target, modifiers?, tab_id? | Triple left-click a coordinate or element, which typically selects a line or paragraph. |
hover | target: Target, tab_id? | Move the pointer over a coordinate or element without clicking. |
left_click_drag | from: CoordinateTarget, target: CoordinateTarget, tab_id? | Press at from, drag to target, and release. |
left_mouse_down | target: CoordinateTarget, tab_id? | Press and hold the left button at a coordinate; pair with left_mouse_up for a custom drag. |
left_mouse_up | target: CoordinateTarget, tab_id? | Release the left button at a coordinate. |
mouse_move | target: CoordinateTarget, tab_id? | Move the pointer to a coordinate. |
scroll | target: CoordinateTarget, scroll_direction, scroll_amount?, tab_id? | Scroll at a viewport position. scroll_direction is "up", "down", "left", or "right"; scroll_amount is in scroll-wheel notches, 1 to 10, default 3. |
scroll_to | target: RefTarget, tab_id? | Scroll a referenced element into view. |
| Member | Input | Description |
|---|---|---|
type | text, tab_id? | Type a literal string at the current focus. |
key | text, repeat?, tab_id? | Press a key or chord. text is a single key ("Enter"), a chord joined with + ("ctrl+a"), or a space-separated sequence ("Backspace Backspace"); repeat is 1 to 100, default 1. |
hold_key | text, duration, tab_id? | Hold a key or chord for duration seconds, 0 to 30. |
wait | duration, tab_id? | Pause for duration seconds, 0 to 30. |
| Member | Input | Description |
|---|---|---|
read_page | filter?, depth?, ref?, tab_id? | Return the page's accessibility tree as text with each element tagged with a reference such as [ref_2]. With filter omitted, return every visible element; with "interactive", only visible interactive elements; with "all", also elements outside the viewport. depth caps the tree depth (minimum 1, default 15) and ref scopes the read to that element's subtree. Cap the output at 50,000 characters and say so in the text; Claude then narrows with a smaller depth or a ref. |
find | query, tab_id? | Search for elements matching a natural-language description such as "search field" or "add to cart button", and return up to 20 matches in the same tagged format as read_page. |
get_page_text | tab_id? | Return the page's visible text as plain text, prioritizing the main article content; suited to articles, documentation, and other text-heavy pages. |
| Member | Input | Description |
|---|---|---|
form_input | target: RefTarget, value, tab_id? | Set a form element's value directly. value is a string, number, or boolean; use a boolean for checkboxes and an option's value or visible text for selects. |
file_upload (disabled by default) | target: RefTarget, paths?, document_ids?, tab_id? | Set the files on a file-input element from paths on the executor's filesystem, document_ids your application has staged, or both; at least one is required. See Upload files. |
| Member | Input | Description |
|---|---|---|
read_console (disabled by default) | tab_id? | Return the tab's console entries (log, warning, and error lines) accumulated since the last read, one line per entry. See Read console and network activity. |
read_network (disabled by default) | tab_id? | Return the tab's network requests (method, URL, status, MIME type, timing) since the last read, one line per entry. |
javascript_exec (disabled by default) | text, tab_id? | Run text as JavaScript in the page context and return the value of the last expression as text. See Enable optional members. |
| Member | Input | Description |
|---|---|---|
new_tab | (none) | Open a tab and make it the active tab. |
list_tabs | (none) | Report the tab inventory. |
switch_tab | tab_id (required) | Make tab_id the active tab. |
close_tab | tab_id (required) | Close tab_id. |
On success, each of these returns exactly one browser_state block and no text or image; see Tab management results.
Besides type, the toolset entry accepts configs, cache_control, and allowed_callers; the rules these fields share with the computer use toolset are listed under Client toolsets, and this section covers the browser-specific defaults. configs is an object keyed by member name, and each member's value accepts two fields:
| Field | Default | Meaning |
|---|---|---|
enabled | true, except false for the four optional members | Whether the member is offered to Claude. |
defer_loading | false | Whether the toolset's definition is deferred for tool search. Must resolve to the same value on every enabled member. With the four optional members left disabled, deferring the toolset means setting it on the other 27; see Client toolsets. |
List only the members you want to change in configs; every member you omit keeps its default. For example, an executor that implements console reads but not low-level pointer or key-hold control turns read_console on and withholds three members:
{
"type": "browser_toolset_20260801",
"configs": {
"read_console": { "enabled": true },
"left_mouse_down": { "enabled": false },
"left_mouse_up": { "enabled": false },
"hold_key": { "enabled": false }
}
}A disabled member disappears from the definition Claude sees; that doesn't guarantee Claude never names it, so your executor still answers such a call with an error result.
Declare the browser use tool alongside your own tools and other Anthropic-provided tools in the same tools array. A custom tool may share a member's name (your own navigate, for example), because toolset_name distinguishes Claude's calls, but no other entry may be named browser, and a request may contain only one browser toolset entry.
You can also declare it alongside the computer use tool, either the toolset or an earlier computer use tool version. The two work independently, each in its own coordinate frame (viewport pixels here, desktop screenshot pixels there), and Claude's calls to members that share a name, such as screenshot or key, are told apart by toolset_name.
Four member tools are disabled by default: javascript_exec and file_upload because they widen what a manipulated page could make Claude do, and read_console and read_network because not every browser automation stack can supply those logs and they widen what page-controlled content reaches Claude. Enable each one with configs (for example, "configs": {"file_upload": {"enabled": true}}) only when your executor implements it and the task needs it.
file_upload sets the files on an <input type="file"> element directly, which is more reliable than driving a native file chooser. Its target is a reference only, because the call needs the element's identity, and it takes paths, document_ids, or both:
paths are file paths on the executor's filesystem, for deployments where the executor can read your application's files directly (the same condition under which you populate a download's path).document_ids are identifiers for files your application has staged for the browser, for deployments where it can't. Your application defines what the identifiers mean; scope their resolution the way you scope paths, to files staged for this task.{
"type": "tool_use",
"id": "toolu_01N7gVzFEfZjLjgsYwnrPgrF",
"name": "file_upload",
"toolset_name": "browser",
"input": {
"target": { "type": "ref", "ref": "ref_12" },
"paths": ["/home/user/uploads/summary.pdf"],
"tab_id": "tab-2"
}
}Claude writes these paths while it's reading untrusted pages, so an unrestricted implementation would let a malicious page direct the upload of any file the executor can read to a site the page controls. Enable the member only when your executor resolves each path (following symlinks and .. segments) and accepts nothing outside a dedicated, allowlisted upload directory that holds only files meant for the task. Don't reuse the browser's download directory for this; if you do, every file a page causes the browser to download becomes uploadable.
javascript_exec runs the expression Claude writes in the page's context and returns the value of the last expression as text; Claude writes an expression, not a return statement. The code runs with the page's full privileges, including its cookies, storage, and same-origin requests. Enable the member only in sessions that hold no credentials, keep the domain allowlist from Security considerations in force, treat the returned value as untrusted input, and log the code Claude emits.
read_console returns the tab's console entries and read_network returns its network requests, each as text with one line per entry accumulated since the previous read of that tab. A console line carries a log, warning, or error entry; a network line carries the method, URL, status, MIME type, and timing. Entries exist only from the moment your browser automation attached to the tab, so an empty result doesn't mean a tab that was already open had no traffic.
These members let Claude diagnose a misbehaving page (a failed request behind a spinner, a script error behind a dead button) without repeated screenshots. Console and network entries are page-controlled and often contain secrets such as tokens in request URLs, so redact credential-like values you don't want in Claude's context and truncate very long entries before returning them.
browser_stateClaude addresses tabs by tab_id, your application is the source of truth for which tabs exist, and you report that state in a browser_state content block that Claude never sees directly: the API renders the text Claude reads from it.
{
"type": "browser_state",
"tabs": [
{
"tab_id": "tab-1",
"title": "Documentation",
"url": "https://example.com/docs",
"active": true
},
{ "tab_id": "tab-2", "title": "Pricing", "url": "https://example.com/pricing" }
]
}tabs is the full inventory of open tabs after the call, not a delta. It may be empty; whenever it isn't, exactly one entry carries "active": true.state_changes (not shown here) reports side effects of the call: a tab_opened entry for each tab the call opened that's still open when it finishes, whose tab_id must also appear in tabs, and download events. Omit the field when there's nothing to report; an empty array is rejected.tool_result, and never on a result with is_error: true. You express "no tab state to report" by omitting the block.tabs into text for Claude as the next two sections describe; download entries in state_changes are validated but not rendered.You assign tab_id values. Any stable string works, such as your automation library's page identifier or your own counter, as long as you don't reuse a tab_id while a tab with that identifier is still listed as open in an earlier result. The API enforces these limits on the block:
tab_id, title, and url may be at most 4,096 characters, tab_id must be non-empty, and none may contain control characters (including newlines) or Unicode line or paragraph separators.tab_id Claude passes to switch_tab and close_tab, because the API renders it into the result text, so answer a call whose tab_id violates them with an error result instead of a browser_state block.For new_tab, switch_tab, close_tab, and list_tabs, a successful result's content is exactly one browser_state block with no text or image, and the API writes the text Claude sees. A new_tab result's block must also carry exactly one tab_opened state change whose tab_id matches the entry marked active: true.
| Member | Text Claude sees |
|---|---|
switch_tab | Switched to tab {tab_id}, taken from the call's input.tab_id |
close_tab | Closed tab {tab_id}, taken from the call's input.tab_id |
new_tab | Created new tab with tab_id: {tab_id}, URL: {url}. It is now the current tab., taken from the entry marked active: true |
list_tabs | Available tabs: followed by one line per tab, or No tabs available when tabs is empty |
A list_tabs result whose block lists two tabs with the first one active renders as follows, with each line indented two spaces and (current) appended to the active tab only:
Available tabs:
• tab_id tab-1: "Documentation" (https://example.com/docs) (current)
• tab_id tab-2: "Pricing" (https://example.com/pricing)An error result for one of these members is the reverse: ordinary error text in content, is_error: true, and no browser_state block.
For example, when Claude calls new_tab (its input is empty), your executor opens the tab, makes it active, and returns the inventory with one tab_opened entry:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01WvHSbQVV9j5nWGvTmk4vNL",
"toolset_name": "browser",
"content": [
{
"type": "browser_state",
"tabs": [
{ "tab_id": "tab-1", "title": "Documentation", "url": "https://example.com/docs" },
{ "tab_id": "tab-2", "title": "Pricing", "url": "https://example.com/pricing" },
{ "tab_id": "tab-3", "title": "", "url": "about:blank", "active": true }
],
"state_changes": [{ "type": "tab_opened", "tab_id": "tab-3" }]
}
]
}
]
}Claude sees Created new tab with tab_id: tab-3, URL: about:blank. It is now the current tab. Report the URL the tab was opened at, as here, not one it later redirects to; later results report the tab's then-current URL.
On every other member the block is optional: send it when the set of open tabs, the active tab, or a tab's title or URL changed, or when there are state_changes to report, and always include the full tabs inventory. When a result carries both text and a browser_state block, the API appends a Tab Context footer to that result's text, separated from your text by a blank line, so Claude receives the new state without a separate list_tabs call:
Tab Context:
- Executed on tab_id: tab-1
- Available tabs:
• tab_id tab-1: "Documentation" (https://example.com/docs)
• tab_id tab-2: "Pricing" (https://example.com/pricing)Executed on names the tab the call ran on, which is its tab_id input when present and otherwise the active tab, and the footer's tab lines carry no (current) marker. Don't append this text yourself; send the structured block and let the API render it. The footer is deduplicated, so identical tab state isn't rendered again on later results and populating the block liberally costs nothing.
Three cases render no footer even when the block is present:
zoom result.text block (an image-only screenshot result, for example). Nothing is rendered or remembered for that result; the tab context appears on the next result that carries both text and a browser_state block, so include a short text block alongside the image when you want Claude to see a tab change on that same result.tabs list is empty on a call that carried no tab_id, because there's no tab to name.For example, when Claude clicked the "Pricing" link (ref_5) earlier in this session, the page opened it in a new tab Claude didn't ask for, and without a report Claude would have to call list_tabs to discover it. Return the click's acknowledgment plus a block whose state_changes names the opened tab, marking whichever tab your executor left active:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01EgTXj1FjE2FCTt2zNFWLao",
"toolset_name": "browser",
"content": [
{ "type": "text", "text": "Clicked element ref_5." },
{
"type": "browser_state",
"tabs": [
{
"tab_id": "tab-1",
"title": "Documentation",
"url": "https://example.com/docs",
"active": true
},
{ "tab_id": "tab-2", "title": "Pricing", "url": "https://example.com/pricing" }
],
"state_changes": [{ "type": "tab_opened", "tab_id": "tab-2" }]
}
]
}
]
}Claude sees Clicked element ref_5. followed by the Tab Context footer shown earlier. A tab opened during a call that failed gets no tab_opened entry, because error results carry no browser_state; it appears in the tabs inventory of the next successful result instead. In a batch, attach the block to the result of the call during which the change happened, and give every successful tab-management result its own block even when an earlier result in the same turn reported the same state.
When a click or navigation starts a file download, report it in state_changes on the result of the call during which it happened, correlated across results by a download_id you assign. Downloads run asynchronously and can span several results, so there are three event types:
type | Fields | When to send |
|---|---|---|
download_started | download_id, url | On the result of the call during which the download began. url is the final URL the file is served from, after redirects. |
download_completed | download_id, url, path?, size_bytes? | On the result of whichever later call is running when the download finishes. Include path only when another tool in the same environment (for example, the bash tool or file_upload) can read the file there; otherwise download_id is the download's only identifier. |
download_failed | download_id, url, error? | When the download fails or is canceled, with the reason in error if the browser provides one. |
The API validates these entries but doesn't render them into text Claude sees, so when Claude needs to act on the file, also mention the file name or path in the same result's text block.
For example, a click on "Download price list (CSV)" (ref_8) in the Pricing tab starts a download, so the click's result carries a download_started entry with download_id "dl-1" and the file's URL. The download finishes while a later screenshot call is running, so that result's content holds the image, a text block such as Screenshot captured. Download complete: /home/user/downloads/price-list.csv (48,213 bytes)., and this browser_state block reporting the completion under the same download_id:
{
"type": "browser_state",
"tabs": [
{ "tab_id": "tab-1", "title": "Documentation", "url": "https://example.com/docs" },
{
"tab_id": "tab-2",
"title": "Pricing",
"url": "https://example.com/pricing",
"active": true
}
],
"state_changes": [
{
"type": "download_completed",
"download_id": "dl-1",
"url": "https://example.com/pricing/price-list.csv",
"path": "/home/user/downloads/price-list.csv",
"size_bytes": 48213
}
]
}Download reports follow these rules:
download_id in a single block, so a download that starts and finishes during the same call reports only download_completed.state_changes on an is_error: true result; report a download event that occurred during a failed call on the next successful result.state_changes isn't an inventory of downloads in progress; report each event once.type declares. size_bytes is a non-negative integer, download_id is non-empty, and download_id, url, path, and error are each at most 4,096 characters with no control characters or Unicode line or paragraph separators. The url comes from the remote server and often carries signed query-string credentials after redirects, so strip query parameters you don't want in Claude's context and sanitize it before reporting it or using it in a filesystem path.Report a failed call to Claude as an ordinary error result: is_error: true, text content that says what went wrong, toolset_name echoed, and no browser_state block.
Make error text specific, because Claude reads it and adapts: Error: Navigation to https://example.com/status timed out after 30 seconds. The page may be unavailable. gives Claude something to act on where a bare Error: navigation failed doesn't. Other common cases:
The API validates the toolset entry and every member tool_use and tool_result block in the conversation. When one is malformed, the API returns an invalid_request_error before Claude runs. In the following table, the left column names what you sent.
| Request | Why it fails and what to do |
|---|---|
An option or combination the toolset entry doesn't accept, for example, a name, strict: true, input_examples, defer_loading on the entry itself, a configs key that isn't a member name, a field other than enabled or defer_loading in a member's configs value (Configure the toolset), enabled members whose defer_loading values differ (Configure the toolset), a configs that leaves no member enabled, a code execution caller in allowed_callers, the legacy fine-grained-tool-streaming-2025-05-14 beta header on the request, a tool_choice of type tool naming browser or a member, or a second browser toolset entry or another tool named browser | These aren't supported on client toolsets. See Client toolsets for each rule and its alternative. |
A tool_result answering a member call without "toolset_name": "browser" or with a different value, or toolset_name on a result whose call wasn't a member call | Echo toolset_name exactly on member results, and only on them. |
A member tool_use from an earlier turn with no matching tool_result | Answer every member call, including the ones you didn't run after a failure. |
A content block other than text, image, or browser_state in a member result | Member results accept only those three block types. |
A browser_state block that breaks a rule in Track tabs with browser_state, for example, one on an is_error: true result or on a result that doesn't answer a browser member call, more than one in a result, a non-empty tabs without exactly one active: true entry, a duplicate tab_id, an empty state_changes array, a tab_opened whose tab_id isn't in tabs, two state changes for one download_id or a state-change field its type doesn't declare (Report downloads), or a field over its limits | Fix the block. "Nothing to report" is expressed by omitting the block or the state_changes field, never by an empty value. |
A successful new_tab, switch_tab, close_tab, or list_tabs result whose content isn't exactly one browser_state block, or a new_tab result without exactly one tab_opened matching the active tab | The API renders these results from the block and needs it in that exact shape; see Tab management results. |
An image in a result over your model's image size limits, or over the stricter per-image limit that applies once the request holds more than 20 images, counting screenshots and zoom images in earlier results | The API doesn't downscale toolset images. Resize screenshots before returning them (Size screenshots to fit image limits). |
A model that doesn't support browser_toolset_20260801 | See Compatibility for the supported models. |
input arrives as one complete input_json_delta (Client toolsets).read_console and read_network depend on your browser automation: They report only what it can capture, and only from the moment it attached to a tab.Browser use follows the standard tool use pricing. When using the browser use tool:
Toolset definition overhead: Declaring browser_toolset_20260801 with its default members adds about 6,600 input tokens to a request (about 6,610 on Claude Fable 5, Claude Mythos 5, Claude Opus 5, and Claude Opus 4.8, and about 6,670 on Claude Sonnet 5), which covers the member tool definitions and the tool use system prompt. Enabling all four optional members adds about 880 tokens, and disabling members with configs reduces the count. The exact count for a request is reported in the response usage, and you can estimate it in advance with the token counting endpoint.
Additional token consumption:
The browser session, downloads, and uploaded files stay in your environment; the screenshots, page text, and tab state you return are part of your API request content and follow the standard retention policy, or your ZDR arrangement if you have one. The browser use tool is ZDR eligible; see API and data retention for retention periods and eligibility across features.
Give Claude control of a full desktop when the task leaves the browser; its implementation guidance applies to browser executors too.
Format tool_result blocks, return images and errors, and continue the conversation.
Browse client toolsets and every other Anthropic-provided tool, with their versions and parameters.
| Supported models |
|
|---|---|
| Supported platforms |
|
Was this page helpful?