Input Controls
Input controls are interactive UI elements (such as text fields, file pickers, and dropdowns) that allow users to provide data to scrapers. With minimal JavaScript, you can create complex forms, making your desktop extractors more user-friendly.
Key Concepts​
- Scraper-Input Binding: Each scraper's controls are defined in a matching
.js
file within theinputs/
folder. For example,stockPriceScraper.js
for a scraper namedstockPriceScraper
. - Dynamic UI: Change controls dynamically using
isShown
orisDisabled
to hide or disable fields based on user input. - Validation: Enforce mandatory fields with
isRequired
or implement custom validation logic (e.g., regex checks) usingvalidate
.
Types of Controls​
text
​
A single-line text field.
Example
.text("stock_symbol", {
placeholder: "Enter a stock symbol (e.g., AAPL)",
defaultValue: "AAPL",
isRequired: true,
})
listOfTexts
​
Multiple single-line text fields. Use the optional limit
parameter to cap the maximum number of items that can be entered.
Example
.listOfTexts("search_queries", {
placeholder: "python",
defaultValue: ["python", "javascript"],
limit: 5, // maximum 5 queries
isRequired: true, // at least 1 query must be provided
})
link
​
A single-line URL field.
Example
.link("website", {
placeholder: "https://example.com",
})
listOfLinks
​
Multiple single-line URL fields. Use the optional limit
parameter to cap the maximum number of links that can be entered.
Example
.listOfLinks("product_urls", {
placeholder: "https://example.com/product/item",
limit: 5, // maximum 5 links
isRequired: true, // at least 1 link must be provided
})
number
​
A numeric input (integer or float). The placeholder
, min
, max
, and defaultValue
parameters are optional.
Example
.number("max_results", {
placeholder: 100,
min: 1,
max: 1000,
defaultValue: 50,
})
numberGreaterThanOrEqualToZero
​
A number field that enforces a minimum value of 0.
Example
.numberGreaterThanOrEqualToZero("retry_delay_in_seconds", {
placeholder: 5,
})
numberGreaterThanOrEqualToOne
​
A number field that enforces a minimum value of 1.
Example
.numberGreaterThanOrEqualToOne("retries", {
placeholder: 3,
})
select
​
A single-select dropdown menu. It requires an options
array.
Example
.select("category", {
options: [
{ value: "tech", label: "Technology" },
{ value: "robotics", label: "Robotics" },
{ value: "ai", label: "AI" },
],
defaultValue: "tech",
})
multiSelect
​
A multi-select dropdown menu. It requires an options
array. The optional limit
parameter sets the maximum number of selections.
Example
.multiSelect("tags", {
options: [
{ value: "tech", label: "Technology" },
{ value: "robotics", label: "Robotics" },
{ value: "ai", label: "AI" },
],
defaultValue: ["tech", "ai"],
})
checkbox
​
A Boolean checkbox. Defaults to false
if no defaultValue
is provided.
Example
.checkbox("include_reviews", {
defaultValue: true,
})
textarea
​
A multi-line text field.
Example
.textarea("description", {
placeholder: "Enter a detailed description",
})
switch
​
A Boolean toggle switch (an alternative to checkbox
). Defaults to false
if no defaultValue
is provided.
Use switch
instead of checkbox
when the toggle is meant to reveal hidden controls via isShown. For example, showing Advanced Settings.
Example
.switch("show_browser")
search
​
A text input with search-specific styling.
Example
.search("search_query", {
isRequired: true,
})
choose
​
Displays options as clickable buttons (an alternative to select
). It requires an options
array.
Use choose
instead of select
when you have fewer than 3 options for better user experience.
Example
.choose("theme", {
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
],
defaultValue: "light",
})
filePicker
​
Allows users to upload one or more files. Supports the accept
, multiple
, and limit
parameters.
Example
/**
* @typedef {import('botasaurus-controls').Controls} Controls
* @typedef {import('botasaurus-controls').FileTypes} FileTypes
*/
const { FileTypes } = require('botasaurus-controls');
/**
* @param {Controls} controls
*/
function getInput(controls) {
controls.filePicker("product_images", {
accept: FileTypes.IMAGE, // restrict to image files ('jpeg','jpg','png','gif','bmp','svg','webp')
limit: 20, // maximum 20 files
isRequired: true, // at least 1 file is required
})
}
To allow only one file to be selected, set multiple
to false
:
/**
* @typedef {import('botasaurus-controls').Controls} Controls
* @typedef {import('botasaurus-controls').FileTypes} FileTypes
*/
const { FileTypes } = require('botasaurus-controls');
/**
* @param {Controls} controls
*/
function getInput(controls) {
controls.filePicker("product_image", {
accept: FileTypes.IMAGE,
multiple: false,
isRequired: true, // a file is required
})
}
section
​
Groups related controls into a collapsible section. Keep in mind that sections cannot be nested inside other sections.
Example
.section("User Details", (section) => {
section
.text("first_name", {
placeholder: "John",
isRequired: true,
})
.text("last_name", {
placeholder: "Doe",
isRequired: true,
})
.number("age", {
min: 18,
isRequired: true,
})
})
addLangSelect
​
A prebuilt language dropdown with 187 languages.
Example
.addLangSelect({
label: "Preferred Language",
defaultValue: "en",
})
The selected language is accessible via data["lang"]
in the scraping function.
addCountrySelect
​
A prebuilt country dropdown with 252 countries.
Example
.addCountrySelect({
label: "Target Country",
defaultValue: "US",
})
The selected country is accessible via data["country"]
in the scraping function.
addProxySection
​
Adds a section with proxy controls.
Example (single proxy)
.addProxySection()
Set isList
to true
to allow users to enter multiple proxies.
Example (proxy list)
.addProxySection({
isList: true,
})
Also, you can get the proxy (or proxy list) from the proxy
field in the metadata:
const taskScraper = task({
name: "taskScraper",
run: ({ data, metadata }) => {
const proxy = metadata["proxy"]
return data
},
})
Control Options​
All controls support a common set of options that allow you to customize their label, default value, validation logic, visibility, and more.
label
​
The label is displayed above the control. If omitted, the control ID (e.g., stock_symbol
) is shown in Title Case.
Example
.text("stock_symbol", {
label: "Ticker Symbol",
})
defaultValue
​
Sets the initial value for the control when the form loads.
Example
.select("category", {
options: [
{ value: "tech", label: "Technology" },
{ value: "robotics", label: "Robotics" },
{ value: "ai", label: "AI" },
],
defaultValue: "tech",
})
isRequired
​
Enforces mandatory field validation. This can be a boolean value or a function that decides based on other inputs.
Example - Boolean isRequired
.link("website", {
isRequired: true,
})
Example - Dynamic Requirement
.text("phone_number", {
isRequired: (data) => data["contact_method"] === "phone", // Required only if contact_method is phone
})
validate
​
A custom validation function that returns an error message if validation fails. It can return a string or an array of strings for multiple errors.
Note:
- A control value can be
null
, but is neverundefined
, so you never need to check forundefined
. - If a control is hidden or disabled, validation is skipped.
- If a control fails the
isRequired
check, validation is skipped. This is why we don't need to check fornull
in the second multiple errors example.
Example - Validate an Email Address
.text("email", {
validate: (value) => {
const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (!emailRegex.test(value)) {
return "Please enter a valid email address"
}
},
})
Example - Multiple Errors
.number("age", {
isRequired: true,
validate: (value) => {
const errors = []
if (value < 18) {
errors.push("Must be 18 or older")
}
if (!Number.isInteger(value)) {
errors.push("Age must be a number")
}
return errors
},
})
helpText
​
Shows a question mark icon that displays help text when hovered over.
Example
.text("api_key", {
label: "API Key",
helpText: "Find API key in Dashboard → Settings → API"
})
isDisabled
​
Dynamically disables the control based on other inputs.
Example
.checkbox("use_custom_settings", {
label: "Use Custom Settings"
})
.number("timeout", {
label: "Timeout (seconds)",
isDisabled: (data) => !data["use_custom_settings"],
})
disabledMessage
​
Tooltip explaining why a control is disabled. It's recommended to use this option when using isDisabled
.
Example
.checkbox("use_custom_settings", {
label: "Use Custom Settings"
})
.number("timeout", {
label: "Timeout (seconds)",
isDisabled: (data) => !data["use_custom_settings"],
disabledMessage: "Enable custom settings to set this value"
})
isShown
​
Conditionally shows or hides a control based on other inputs. This is often better than isDisabled
for optional sections like "Advanced settings" because it creates a cleaner UI by hiding complexity until needed.
A switch
or choose
control is recommended to toggle visibility.
Example
.switch('enable_reviews_extraction')
.numberGreaterThanOrEqualToZero('max_reviews', {
label: 'Max Reviews per Place (Leave empty to extract all reviews)',
placeholder: 20,
isShown: (data) => data['enable_reviews_extraction'],
defaultValue: 20,
})
.choose('reviews_sort', {
label: "Sort Reviews By",
isRequired: true,
isShown: (data) => data['enable_reviews_extraction'],
defaultValue: 'newest',
options: [
{ value: 'newest', label: 'Newest' },
{ value: 'most_relevant', label: 'Most Relevant' },
{ value: 'highest_rating', label: 'Highest Rating' },
{ value: 'lowest_rating', label: 'Lowest Rating' },
],
})
isMetadata
​
When set to true
, the control's value is passed in the metadata
instead of the data
parameter in the scraping function.
Example
.text("api_key", {
label: "API Key",
isMetadata: true,
})
In the scraper function, use it as follows:
const task = task({
name: "taskScraper",
run: ({ data, metadata }) => {
const scraperId = metadata["api_key"]
}
})
The metadata
parameter is useful when you have enabled caching and want to exclude a field from being included in the cache key, such as api_key
, cookies
, proxy
, etc.
Keep this in mind, as you will learn about caching in a later section.
Real World Example​
The following example demonstrates how to use listOfTexts
, number
, switch
, choose
, section
, and other controls to build an advanced form:
/**
* @typedef {import('botasaurus-controls').Controls} Controls
*/
/**
* @param {Controls} controls
*/
function getInput(controls) {
controls
.listOfTexts('queries', {
defaultValue: ["Web Developers in Bangalore"],
placeholder: "Web Developers in Bangalore",
label: 'Search Queries',
isRequired: true,
})
.section("Email and Social Links Extraction", (section) => {
section.text('api_key', {
placeholder: "2e5d346ap4db8mce4fj7fc112s9h26s61e1192b6a526af51n9",
label: 'Email and Social Links Extraction API Key',
helpText: 'Enter your API key to extract email addresses and social media links.',
isMetadata: true,
})
})
.section("Reviews Extraction", (section) => {
section
.switch('enable_reviews_extraction', {
label: "Enable Reviews Extraction",
})
.numberGreaterThanOrEqualToZero('max_reviews', {
label: 'Max Reviews per Place (Leave empty to extract all reviews)',
placeholder: 20,
isShown: (data) => data['enable_reviews_extraction'],
defaultValue: 20,
})
.choose('reviews_sort', {
label: "Sort Reviews By",
isRequired: true,
isShown: (data) => data['enable_reviews_extraction'],
defaultValue: 'newest',
options: [
{ value: 'newest', label: 'Newest' },
{ value: 'most_relevant', label: 'Most Relevant' },
{ value: 'highest_rating', label: 'Highest Rating' },
{ value: 'lowest_rating', label: 'Lowest Rating' },
],
})
})
.section("Language and Max Results", (section) => {
section
.addLangSelect()
.numberGreaterThanOrEqualToOne('max_results', {
placeholder: 100,
label: 'Max Results per Search Query (Leave empty to extract all places)',
})
})
.section("Geo Location", (section) => {
section
.text('coordinates', {
placeholder: '12.900490, 77.571466',
})
.numberGreaterThanOrEqualToOne('zoom_level', {
label: 'Zoom Level (1-21)',
defaultValue: 14,
placeholder: 14,
})
})
}