> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stagehand.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Prompting best practices

> Write effective prompts for reliable Stagehand automation

Good prompts make Stagehand reliable. Bad prompts cause failures. Here's how to write prompts that work consistently.

## Act method

Use `act()` for single actions on web pages. Each action should be focused and clear.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Single, specific actions
    await stagehand.act("click the 'Add to Cart' button");
    await stagehand.act("type 'user@example.com' into the email field");

    // Bad - Multiple actions combined
    await stagehand.act("fill out the form and submit it");
    await stagehand.act("login with credentials and navigate to dashboard");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Single, specific actions
    await stagehand.act("click the 'Add to Cart' button")
    await stagehand.act("type 'user@example.com' into the email field")

    # Bad - Multiple actions combined
    await stagehand.act("fill out the form and submit it")
    await stagehand.act("login with credentials and navigate to dashboard")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Single, specific actions
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the 'Add to Cart' button"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type 'user@example.com' into the email field"), nil); err != nil {
    	return err
    }

    // Bad - Multiple actions combined
    if _, err := client.Act(ctx, stagehand.ActInstruction("fill out the form and submit it"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("login with credentials and navigate to dashboard"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Use element types, not colors

Describe elements by their type and function rather than visual attributes like color.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Element types and descriptive text
    await stagehand.act("click the 'Sign In' button");
    await stagehand.act("type into the email input field");

    // Bad - Color-based descriptions
    await stagehand.act("click the blue button");
    await stagehand.act("type into the white input");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Element types and descriptive text
    await stagehand.act("click the 'Sign In' button")
    await stagehand.act("type into the email input field")

    # Bad - Color-based descriptions
    await stagehand.act("click the blue button")
    await stagehand.act("type into the white input")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Element types and descriptive text
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the 'Sign In' button"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type into the email input field"), nil); err != nil {
    	return err
    }

    // Bad - Color-based descriptions
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the blue button"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type into the white input"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Use descriptive language

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Clear element identification
    await stagehand.act("click the 'Next' button at the bottom of the form");
    await stagehand.act("type into the search bar at the top of the page");

    // Bad - Vague descriptions
    await stagehand.act("click next");
    await stagehand.act("type into search");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Clear element identification
    await stagehand.act("click the 'Next' button at the bottom of the form")
    await stagehand.act("type into the search bar at the top of the page")

    # Bad - Vague descriptions
    await stagehand.act("click next")
    await stagehand.act("type into search")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Clear element identification
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the 'Next' button at the bottom of the form"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type into the search bar at the top of the page"), nil); err != nil {
    	return err
    }

    // Bad - Vague descriptions
    if _, err := client.Act(ctx, stagehand.ActInstruction("click next"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type into search"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Choose the right action verbs

* **Click** for buttons, links, checkboxes
* **Type** for text inputs
* **Select** for dropdowns
* **Check/uncheck** for checkboxes
* **Upload** for file inputs

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good
    await stagehand.act("click the submit button");
    await stagehand.act("select 'Option 1' from dropdown");

    // Bad
    await stagehand.act("click submit");
    await stagehand.act("choose option 1");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good
    await stagehand.act("click the submit button")
    await stagehand.act("select 'Option 1' from dropdown")

    # Bad
    await stagehand.act("click submit")
    await stagehand.act("choose option 1")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the submit button"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("select 'Option 1' from dropdown"), nil); err != nil {
    	return err
    }

    // Bad
    if _, err := client.Act(ctx, stagehand.ActInstruction("click submit"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("choose option 1"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Protect sensitive data

Variables keep sensitive information out of prompts and logs. Stagehand shows the model only the variable name and its optional description, then substitutes the real value into the resolved action right before it runs, so the secret never reaches the model and never appears in the logged action.

<Warning>
  Variables are part of the payload that builds the [cache key](/v4/best-practices/caching). Caching is off by default, so the values stay inside the run unless you opt in. Once you enable the `cache` option on create or on a call, the variables you pass travel to the cache service with the rest of the request, so turn it off on the calls that carry credentials.
</Warning>

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Use variables for sensitive data
    await stagehand.act("type %username% into the email field", {
      variables: { username: "user@example.com" },
    });

    // v4 reads no environment variables, so read the secret yourself
    await stagehand.act("type %password% into the password field", {
      variables: { password: process.env.USER_PASSWORD },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Use variables for sensitive data
    await stagehand.act(
        "type %username% into the email field",
        variables={"username": "user@example.com"},
    )

    # v4 reads no environment variables, so read the secret yourself
    await stagehand.act(
        "type %password% into the password field",
        variables={"password": os.environ["USER_PASSWORD"]},
    )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Use variables for sensitive data
    if _, err := client.Act(ctx, stagehand.ActInstruction("type %username% into the email field"), &stagehand.StagehandClientActOptions{
    	ActOptions: stagehand.ActOptions{
    		Variables: stagehand.Variables{
    			"username": stagehand.PrimitiveVariable(stagehand.StringVariable("user@example.com")),
    		},
    	},
    }); err != nil {
    	return err
    }

    // v4 reads no environment variables, so read the secret yourself
    password := os.Getenv("USER_PASSWORD")

    if _, err := client.Act(ctx, stagehand.ActInstruction("type %password% into the password field"), &stagehand.StagehandClientActOptions{
    	ActOptions: stagehand.ActOptions{
    		Variables: stagehand.Variables{
    			"password": stagehand.PrimitiveVariable(stagehand.StringVariable(password)),
    		},
    	},
    }); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

<Warning>
  Set the log level to `off` in your Stagehand config to prevent secrets from appearing in logs.
</Warning>

## Extract method

Use `extract()` to pull structured data from pages. Define clear schemas and provide context.

### Schema best practices

Use descriptive field names, correct types, and detailed descriptions. Field descriptions provide context that helps the model understand exactly what to extract.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Descriptive names, correct types, and helpful descriptions
    const { data: productData } = await stagehand.extract(
      "Extract product information",
      z.object({
        productTitle: z.string().describe("The main product name displayed on the page"),
        priceInDollars: z.number().describe("Current selling price as a number, without currency symbol"),
        isInStock: z.boolean().describe("Whether the product is available for purchase"),
      }),
    );

    // Bad - Generic names, wrong types, no descriptions
    const { data } = await stagehand.extract(
      "Get product details",
      z.object({
        name: z.string(), // Too generic, no context
        price: z.string(), // Should be number
        stock: z.string(), // Should be boolean, no context
      }),
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from pydantic import BaseModel, Field


    # Good - Descriptive names, correct types, and helpful descriptions
    class ProductData(BaseModel):
        product_title: str = Field(description="The main product name displayed on the page")
        price_in_dollars: float = Field(
            description="Current selling price as a number, without currency symbol"
        )
        is_in_stock: bool = Field(description="Whether the product is available for purchase")


    product_data = (await stagehand.extract(
        instruction="Extract product information",
        schema=ProductData,
    )).data


    # Bad - Generic names, wrong types, no descriptions
    class Data(BaseModel):
        name: str   # Too generic, no context
        price: str  # Should be a number
        stock: str  # Should be a boolean, no context


    data = (await stagehand.extract(instruction="Get product details", schema=Data)).data
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Descriptive names, correct types, and helpful descriptions
    type productData struct {
    	ProductTitle   string  `json:"product_title" jsonschema:"description=the main product name displayed on the page"`
    	PriceInDollars float64 `json:"price_in_dollars" jsonschema:"description=current selling price as a number without a currency symbol"`
    	IsInStock      bool    `json:"is_in_stock" jsonschema:"description=whether the product is available for purchase"`
    }

    // Bad - Generic names, wrong types, no descriptions
    type data struct {
    	Name  string `json:"name"`
    	Price string `json:"price"`
    	Stock string `json:"stock"`
    }
    ```
  </Tab>
</Tabs>

### Use proper URL types

Type link fields as URLs so Stagehand resolves them to real addresses instead of the internal element IDs the model chooses.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Tells Stagehand to extract URLs
    const { data } = await stagehand.extract(
      "Extract navigation links",
      z.object({
        links: z.array(z.object({
          text: z.string(),
          url: z.url(), // Required for URL extraction
        })),
      }),
    );

    // Single URL extraction
    const { data: contact } = await stagehand.extract(
      "extract the contact page URL",
      z.object({ contactUrl: z.url() }),
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from pydantic import AnyUrl, BaseModel


    # Good - Tells Stagehand to extract URLs
    class Link(BaseModel):
        text: str
        url: AnyUrl  # Required for URL extraction


    class Links(BaseModel):
        links: list[Link]


    result = (await stagehand.extract(
        instruction="Extract navigation links",
        schema=Links,
    )).data


    # Single URL extraction
    class ContactUrl(BaseModel):
        contact_url: AnyUrl


    contact = (await stagehand.extract(
        instruction="extract the contact page URL",
        schema=ContactUrl,
    )).data
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Tells Stagehand to extract URLs
    type link struct {
    	Text string `json:"text"`
    	URL  string `json:"url" jsonschema:"format=uri"`
    }

    type links struct {
    	Links []link `json:"links"`
    }

    extracted, err := stagehand.Extract[links](ctx, client, "Extract navigation links", nil)
    if err != nil {
    	return err
    }

    for _, l := range extracted.Data.Links {
    	fmt.Println(l.Text, l.URL)
    }
    ```
  </Tab>
</Tabs>

## Observe method

Use `observe()` to discover actionable elements before acting on them.

### Check elements first

Verify elements exist before taking action to avoid errors.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Check for elements first
    const { data: loginButtons } = await stagehand.observe("Find the login button");

    if (loginButtons.length > 0) {
      await page.locator(loginButtons[0].selector).click();
    } else {
      console.log("No login button found");
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Check for elements first
    login_buttons = (await stagehand.observe(instruction="Find the login button")).data

    if login_buttons:
        await page.locator(login_buttons[0].selector).click()
    else:
        print("No login button found")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Check for elements first
    instruction := "Find the login button"
    observed, err := client.Observe(ctx, &instruction, nil)
    if err != nil {
    	return err
    }

    if len(observed.Data) > 0 {
    	if err := page.Locator(observed.Data[0].Selector).Click(ctx, nil); err != nil {
    		return err
    	}
    } else {
    	fmt.Println("No login button found")
    }
    ```
  </Tab>
</Tabs>

### Be specific about element types

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Specific element types
    const { data: submitButtons } = await stagehand.observe("Find submit button in the form");
    const { data: dropdowns } = await stagehand.observe("Find the state dropdown menu");

    // Bad - Too vague
    const { data: elements } = await stagehand.observe("Find submit stuff");
    const { data: things } = await stagehand.observe("Find state selection");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Specific element types
    submit_buttons = (await stagehand.observe(instruction="Find submit button in the form")).data
    dropdowns = (await stagehand.observe(instruction="Find the state dropdown menu")).data

    # Bad - Too vague
    elements = (await stagehand.observe(instruction="Find submit stuff")).data
    things = (await stagehand.observe(instruction="Find state selection")).data
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Specific element types
    submit := "Find submit button in the form"
    submitButtons, err := client.Observe(ctx, &submit, nil)
    if err != nil {
    	return err
    }
    fmt.Println(len(submitButtons.Data))

    dropdown := "Find the state dropdown menu"
    dropdowns, err := client.Observe(ctx, &dropdown, nil)
    if err != nil {
    	return err
    }
    fmt.Println(len(dropdowns.Data))

    // Bad - Too vague
    vague := "Find submit stuff"
    if _, err := client.Observe(ctx, &vague, nil); err != nil {
    	return err
    }

    alsoVague := "Find state selection"
    if _, err := client.Observe(ctx, &alsoVague, nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

## Sequencing multi-step work

Stagehand v4 has no autonomous agent, so multi-step flows are your control flow. That is a feature: you decide the order, the retries, and the stopping condition.

### Navigate first

Don't put navigation inside an instruction. Handle it separately with `goto`.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Navigate first, then act
    await page.goto("https://amazon.com");
    await stagehand.act("type 'wireless headphones' into the search box");

    // Bad - Navigation inside the instruction
    await stagehand.act("go to Amazon and search for headphones");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Navigate first, then act
    await page.goto("https://amazon.com")
    await stagehand.act("type 'wireless headphones' into the search box")

    # Bad - Navigation inside the instruction
    await stagehand.act("go to Amazon and search for headphones")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Navigate first, then act
    if _, err := page.Goto(ctx, "https://amazon.com", nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("type 'wireless headphones' into the search box"), nil); err != nil {
    	return err
    }

    // Bad - Navigation inside the instruction
    if _, err := client.Act(ctx, stagehand.ActInstruction("go to Amazon and search for headphones"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Break work into steps

One instruction per action. Sequence them yourself so each step is independently debuggable and cacheable.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Explicit, ordered steps
    await stagehand.act("type 'wireless headphones' into the search box");
    await stagehand.act("press Enter in the search box");
    await stagehand.act("click the 4 stars and up filter");

    const { data } = await stagehand.extract(
      "Extract the first three results with name and price",
      z.object({
        products: z.array(z.object({ name: z.string(), price: z.string() })),
      }),
    );

    // Bad - One instruction carrying a whole workflow
    await stagehand.act("search for wireless headphones under $100 and add the best rated one to cart");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Explicit, ordered steps
    await stagehand.act("type 'wireless headphones' into the search box")
    await stagehand.act("press Enter in the search box")
    await stagehand.act("click the 4 stars and up filter")


    class Product(BaseModel):
        name: str
        price: str


    class Products(BaseModel):
        products: list[Product]


    result = (await stagehand.extract(
        instruction="Extract the first three results with name and price",
        schema=Products,
    )).data

    # Bad - One instruction carrying a whole workflow
    await stagehand.act(
        "search for wireless headphones under $100 and add the best rated one to cart"
    )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Explicit, ordered steps
    if _, err := client.Act(ctx, stagehand.ActInstruction("type 'wireless headphones' into the search box"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("press Enter in the search box"), nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the 4 stars and up filter"), nil); err != nil {
    	return err
    }

    type product struct {
    	Name  string `json:"name"`
    	Price string `json:"price"`
    }

    type products struct {
    	Products []product `json:"products"`
    }


    extracted, err := stagehand.Extract[products](
    	ctx,
    	client,
    	"Extract the first three results with name and price",
    	nil,
    )
    if err != nil {
    	return err
    }

    for _, p := range extracted.Data.Products {
    	fmt.Println(p.Name, p.Price)
    }

    // Bad - One instruction carrying a whole workflow
    if _, err := client.Act(ctx, stagehand.ActInstruction("search for wireless headphones under $100 and add the best rated one to cart"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

### Include success criteria

Verify each step landed instead of assuming it did.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Good - Verify the outcome
    await stagehand.act("click the add to cart button");

    const { data: cart } = await stagehand.extract(
      "Extract the number of items shown in the cart badge",
      z.object({ itemCount: z.number() }),
    );

    if (cart.itemCount !== 1) {
      throw new Error(`Expected 1 item in cart, found ${cart.itemCount}`);
    }

    // Bad - No validation
    await stagehand.act("add some items to cart");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Good - Verify the outcome
    await stagehand.act("click the add to cart button")


    class Cart(BaseModel):
        item_count: int


    cart = (await stagehand.extract(
        instruction="Extract the number of items shown in the cart badge",
        schema=Cart,
    )).data

    if cart.item_count != 1:
        raise RuntimeError(f"Expected 1 item in cart, found {cart.item_count}")

    # Bad - No validation
    await stagehand.act("add some items to cart")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Good - Verify the outcome
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the add to cart button"), nil); err != nil {
    	return err
    }

    type cart struct {
    	ItemCount int `json:"item_count"`
    }


    result, err := stagehand.Extract[cart](
    	ctx,
    	client,
    	"Extract the number of items shown in the cart badge",
    	nil,
    )
    if err != nil {
    	return err
    }

    if result.Data.ItemCount != 1 {
    	return fmt.Errorf("expected 1 item in cart, found %d", result.Data.ItemCount)
    }

    // Bad - No validation
    if _, err := client.Act(ctx, stagehand.ActInstruction("add some items to cart"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

## Common mistakes to avoid

* **Combining multiple actions:** Keep each `act()` call to one action
* **Using vague descriptions:** Be specific about which elements to interact with
* **Exposing sensitive data:** Always use variables for credentials
* **Skipping validation:** Check results before proceeding

## Testing your prompts

1. **Start simple:** Test basic functionality first
2. **Add complexity gradually:** Build up to complex workflows
3. **Monitor results:** Use logging to understand what's happening
4. **Iterate based on failures:** Refine prompts when they don't work

Remember: Good prompting is iterative. When in doubt, be more specific rather than less.
