Skip to main content

Overview

This example demonstrates how to use Stagehand’s observe() and act() methods to intelligently fill out web forms. The pattern is particularly useful when you need to:
  • Preview actions before executing them
  • Cache form field locations for faster repeated operations
  • Keep sensitive data away from LLM providers
  • Fill forms with custom mapping logic

Basic Form Filling

Here’s a simple example that navigates to Google and performs a search:
import { Stagehand } from "@stagehand/core";

async function example() {
  const stagehand = new Stagehand({
    env: "BROWSERBASE",
    verbose: 1,
  });
  await stagehand.init();
  const page = stagehand.context.pages()[0];
  await page.goto("https://google.com");
  await stagehand.act("type in 'Browserbase'");
  await stagehand.act("press enter");
  await stagehand.close();
}

(async () => {
  await example();
})();

Advanced: Form Filling with Sensitive Data

This example shows how to use observe() to get cacheable actions, then map them to your own data before executing with act():
import { Stagehand } from "@stagehand/core";
import chalk from "chalk";

async function formFillingSensible() {
  const stagehand = new Stagehand({
    env: "BROWSERBASE",
    verbose: 1,
  });
  await stagehand.init();
  const page = stagehand.context.pages()[0];

  // Go to the website and wait for it to load
  await page.goto("https://file.1040.com/estimate/", {
    waitUntil: "networkidle",
    timeoutMs: 30000,
  });

  // Observe the form fields with suggested actions
  const observed = await stagehand.observe(
    "fill all the form fields in the page with mock data. In the description include the field name",
  );

  console.log(
    `${chalk.green("Observe:")} Form fields found:\n${observed
      .map((r) => `${chalk.yellow(r.description)} -> ${chalk.gray(r.selector)}`)
      .join("\n")}`,
  );

  // Create a mapping of 1+ keywords in the form fields to standardize field names
  const mapping = (description: string): string | null => {
    const keywords: { [key: string]: string[] } = {
      age: ["old"],
      dependentsUnder17: ["under age 17", "child", "minor"],
      dependents17to23: ["17-23", "school", "student"],
      wages: ["wages", "W-2 Box 1"],
      federalTax: ["federal tax", "Box 2"],
      stateTax: ["state tax", "Box 17"],
    };

    for (const [key, terms] of Object.entries(keywords)) {
      if (terms.some((term) => description.toLowerCase().includes(term))) {
        return key;
      }
    }
    return null;
  };

  // Fill the form fields with sensible data. This data will only be used in your session and not be shared with LLM providers/external APIs.
  const userInputs: { [key: string]: string } = {
    age: "26",
    dependentsUnder17: "1",
    wages: "54321",
    federalTax: "8345",
    stateTax: "2222",
  };

  const updatedFields = observed.map((candidate) => {
    const key = mapping(candidate.description);
    if (key && userInputs[key]) {
      candidate.arguments = [userInputs[key]];
    }
    return candidate;
  });

  console.log(
    `\n${chalk.green("Sensible Data form inputs:")} Form fields to be filled:\n${updatedFields
      .map(
        (r) =>
          `${chalk.yellow(r.description)} -> ${chalk.blue(r.arguments?.[0] || "no value")}`,
      )
      .join("\n")}`,
  );

  // Fill all the form fields with the sensible candidates
  for (const candidate of updatedFields) {
    await stagehand.act(candidate);
  }
}

(async () => {
  await formFillingSensible();
})();

Key Concepts

observe()

The observe() method returns a list of possible actions as JSON objects. This is useful for:
  • Caching action locations
  • Previewing what Stagehand will do
  • Separating sensitive data from AI model calls

act()

The act() method can accept:
  • A natural language instruction (e.g., "type in 'hello'")
  • An action object from observe()

Custom Mapping

By mapping form field descriptions to your own data structure, you can:
  • Keep sensitive information local
  • Standardize field names across different forms
  • Apply custom validation logic

Next Steps

Build docs developers (and LLMs) love