Automate form submissions at scale with Browserbase. Handle logins, registrations, data entry, and checkouts using Stagehand or Playwright in reliable cloud browsers.
Automate form submissions using cloud browsers that handle dynamic content, authentication, and bot protection. Browserbase gives you reliable infrastructure for form filling workflows, whether you’re using Stagehand or Playwright.
Scale form automation across concurrent sessions without managing infrastructure
Looking to deploy form automation as an API?Functions let you package form automation workflows as cloud functions that can be invoked via webhook or API call, perfect for integrating form submissions into your applications.
To demonstrate how to automate form submissions using Browserbase, you can use a sample Google Form designed specifically for this tutorial: Google FormThis form collects responses in various formats:
import { Stagehand } from "@browserbasehq/stagehand";import { z } from "zod";import dotenv from "dotenv";dotenv.config();async function main() { const stagehand = new Stagehand({ env: "BROWSERBASE", verbose: 0, }); await stagehand.init(); const page = stagehand.context.pages()[0]; async function fillForm(inputs: any) { // Navigate to the form await page.goto("https://forms.gle/f4yNQqZKBFCbCr6j7"); // Select the superpower radio button await stagehand.act(`Select the superpower: ${inputs.superpower}`); // Select the features used checkboxes await stagehand.act("Select the features used: " + inputs.features_used.join(", ")); // Fill in the coolest build text field await stagehand.act("Fill in the coolest_build field with the following value: " + inputs.coolest_build); // Submit the form await stagehand.act("Click the submit button"); await page.waitForTimeout(5000); // Extract to log the status of the form const status = await stagehand.extract({instruction: "Extract the status of the form", schema: z.object({status: z.string()})}); console.log(status); await stagehand.close(); } const inputs = { "superpower": "Invisibility", "features_used": [ "Verified", "Proxies", "Session Replay" ], "coolest_build": "A bot that automates form submissions across multiple sites.", } await fillForm(inputs);}main().catch(console.error);
import { chromium } from "playwright-core";import Browserbase from "@browserbasehq/sdk";import { config } from "dotenv";config();async function createSession() { const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! }); const session = await bb.sessions.create({ // Add configuration options here }); return session;}async function fillForm(inputs: any) { const session = await createSession() const browser = await chromium.connectOverCDP(session.connectUrl); // Getting the default context to ensure the sessions are recorded. const defaultContext = browser.contexts()[0]; const page = defaultContext?.pages()[0]; console.log(`View session recording at https://browserbase.com/sessions/${session.id}`,); // Navigate to page await page.goto("https://forms.gle/f4yNQqZKBFCbCr6j7"); // fill superpower await page.locator(`[role="radio"][data-value="${inputs.superpower}"]`).click(); await page.waitForTimeout(1000); // fill features_used for (const feature of inputs.features_used) { await page.locator(`[role="checkbox"][aria-label="${feature}"]`).click(); } await page.waitForTimeout(1000); // fill coolest_build await page.locator('input[jsname="YPqjbf"]').fill(inputs.coolest_build); await page.waitForTimeout(1000); // click submit button await page.locator('div[role="button"]:has-text("Submit")').click(); // wait 10 seconds await page.waitForTimeout(10000); console.log("Shutting down..."); await page.close(); await browser.close();}const inputs = { "superpower": "Invisibility", "features_used": [ "Verified", "Proxies", "Session Replay" ], "coolest_build": "A bot that automates form submissions across multiple sites.",}fillForm(inputs);
import osimport asynciofrom stagehand import AsyncStagehandfrom dotenv import load_dotenvload_dotenv()async def main(): client = AsyncStagehand( browserbase_api_key=os.environ["BROWSERBASE_API_KEY"], model_api_key=os.environ["MODEL_API_KEY"], ) session = await client.sessions.create(model_name="google/gemini-2.5-flash") async def fill_form(inputs): # Navigate to the form await session.navigate(url="https://forms.gle/f4yNQqZKBFCbCr6j7") # Select the superpower radio button await session.act(input=f"Select the superpower: {inputs['superpower']}") # Select the features used checkboxes await session.act(input="Select the features used: " + ", ".join(inputs["features_used"])) # Fill in the coolest build text field await session.act(input="Fill in the coolest_build field with the following value: " + inputs["coolest_build"]) # Submit the form await session.act(input="Click the submit button") await fill_form({ "superpower": "Invisibility", "features_used": [ "Verified", "Proxies", "Session Replay" ], "coolest_build": "A bot that automates form submissions across multiple sites.", }) await session.end()if __name__ == "__main__": asyncio.run(main())
import osfrom playwright.sync_api import sync_playwrightfrom browserbase import Browserbasefrom dotenv import load_dotenvload_dotenv()def create_session(): """Creates a Browserbase session.""" bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"]) session = bb.sessions.create( # Add configuration options here if needed ) return sessiondef fill_form(inputs): """Automates form filling using Playwright with Browserbase.""" session = create_session() print(f"View session recording at https://browserbase.com/sessions/{session.id}") with sync_playwright() as p: browser = p.chromium.connect_over_cdp(session.connect_url) # Get the default browser context and page context = browser.contexts[0] page = context.pages[0] # Navigate to the form page page.goto("https://forms.gle/f4yNQqZKBFCbCr6j7") # Select superpower page.locator(f'[role="radio"][data-value="{inputs["superpower"]}"]').click() page.wait_for_timeout(1000) # Select features used for feature in inputs["features_used"]: page.locator(f'[role="checkbox"][aria-label="{feature}"]').click() page.wait_for_timeout(1000) # Fill in coolest build page.locator('input[jsname="YPqjbf"]').fill(inputs["coolest_build"]) page.wait_for_timeout(1000) # Click submit button page.locator('div[role="button"]:has-text("Submit")').click() # Wait 10 seconds page.wait_for_timeout(10000) print("Shutting down...") page.close() browser.close()if __name__ == "__main__": inputs = { "superpower": "Invisibility", "features_used": [ "Verified", "Proxies", "Session Replay" ], "coolest_build": "A bot that automates form submissions across multiple sites.", } fill_form(inputs)
This example form is for testing purposes - feel free to submit responses multiple times while experimenting.
Target specific form elements with waitForSelector rather than using arbitrary timeouts. This ensures your automation proceeds only when the form is ready.
Node.js
Python
// Wait for a specific input field to appearawait page.waitForSelector('input[name="email"]', { timeout: 10000 });await page.fill('input[name="email"]', "user@example.com");
# Wait for a specific input field to appearpage.wait_for_selector('input[name="email"]', timeout=10000)page.fill('input[name="email"]', "user@example.com")
Forms that reveal fields based on previous selections need sequential interaction. Wait for each new section to render before continuing.
Node.js
Python
// Select an option that reveals additional fieldsawait page.selectOption('select[name="account_type"]', "business");// Wait for the dynamically revealed fieldsawait page.waitForSelector('input[name="company_name"]', { state: "visible" });await page.fill('input[name="company_name"]', "Acme Corp");
# Select an option that reveals additional fieldspage.select_option('select[name="account_type"]', "business")# Wait for the dynamically revealed fieldspage.wait_for_selector('input[name="company_name"]', state="visible")page.fill('input[name="company_name"]', "Acme Corp")
Check for confirmation messages, URL changes, or response status after submitting a form.
Node.js
Python
await page.click('button[type="submit"]');// Check for a success message or URL changeawait page.waitForSelector("text=Your response has been recorded.");const currentUrl = page.url();console.log(`Submission complete. Redirected to: ${currentUrl}`);
page.click('button[type="submit"]')# Check for a success message or URL changepage.wait_for_selector("text=Your response has been recorded.")current_url = page.urlprint(f"Submission complete. Redirected to: {current_url}")