# HTML Form Field Map (`sapph1re/html-form-field-map`) Actor

Map native HTML form controls to their actual static form, labels, fieldsets and ordered choice labels from exact public pages.

- **URL**: https://apify.com/sapph1re/html-form-field-map.md
- **Developed by:** [Roman V](https://apify.com/sapph1re) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## HTML Form Field Map

Turn exact public HTML pages into a field-mapping worksheet. Each dataset row is one native control, connected to its form, associated labels, physical fieldsets and ordered choice labels. This can help when documenting or migrating a static form without manually joining separate text extracts.

Use the live listing to check availability, the current build and active pricing.

### Input

```json
{
  "pageUrls": ["https://httpbin.org/forms/post"],
  "formSelector": "form",
  "maxFields": 20
}
```

The default and an empty object use HTTPBin's public pizza-form demonstration. The Actor only retrieves HTML and robots.txt. It never follows the form's submission action. Provide only public URLs you are authorized to read, after checking the site's terms. robots.txt is enforced; it does not replace authorization or terms review.

`pageUrls` accepts one to ten exact HTTP(S) URLs on default ports. Duplicates, including fragment variants, are fetched once. `formSelector` accepts `form`, `#id`, `form#id`, `.class` or `form.class`; identifiers use ASCII letters, digits, underscores and hyphens, starting with a letter or underscore. It chooses native forms only. Externally associated controls are retained. `maxFields` defaults to 20 and accepts 0 through 500. Zero produces no source reads.

### Dataset rows

Rows preserve document order, including distinct controls with the same name or label. `pageId`, `formId` and `controlId` are deterministic for the same source URL and static element positions. They are not durable identifiers across HTML edits. `pageIndex` and `formIndex` are zero-based. `controlIndex` counts native input/select/textarea/button elements in source order, including excluded hidden and unowned controls. Template, script and foreign-content subtrees are outside the static native tree.

- `sourceUrl`, `formHtmlId`, `controlHtmlId`, `tag`, declared `type` and `name` describe provenance and structure. Missing attributes remain null.
- `ownership` distinguishes an explicit `form` attribute from ancestor ownership. A missing or ambiguous explicit owner never falls back to an ancestor.
- `labels` preserves each explicit or enclosing association, label index, optional HTML ID and normalized text. Empty labels remain empty. Nearby text, placeholders and button text do not become invented labels.
- `fieldsets` records physical ancestors from outermost to innermost, each first direct legend and the fieldset's own disabled declaration. Physical ancestry can differ from form ownership.
- `flags` reports the presence and raw spelling of disabled, required and readonly. A raw value of `false` still means the attribute is declared. These are not browser-effective validity, visibility or disabled-state results.
- `constraints` preserves declared min/max/step, length, pattern, accept, size, rows and cols strings. `multipleDeclared` is structural metadata.
- `optgroups` has ordered numeric group identities, labels and each group's own disabled declaration. `options` has source order, permitted display label, numeric group identity and each option's own disabled declaration. Repeated labels and groups remain distinct.

The full machine-readable row contract is `field_schema.json`.

### Exclusions and qualified outcomes

No values are emitted: input defaults, textarea contents, password values, hidden fields/tokens, checked or selected state, option submission values, form-action URLs and embedded scripts are excluded. Password fields may retain structural metadata. No browser, JavaScript, shadow DOM, login, cookie store, CAPTCHA, typing, submission, form-action probing, crawl or per-site rules are used.

This is a bounded static mapper, not a browser parser, accessibility-name algorithm, submission simulator or certification tool. Duplicate IDs qualify associations instead of guessing them. Nested/unclosed forms or labels, duplicate attributes and form placement requiring table/select repair are unsupported. Custom widgets are outside scope. Absence from static HTML does not prove that the rendered page has no form.

`OUTPUT` contains aggregate page outcomes, limits and the delivery receipt, with no free field inventory. Pages report USEFUL, EMPTY, PARTIAL, UNSUPPORTED, FAILED or NOT\_FETCHED. A healthy sibling's complete rows survive another page's failure. An oversized field is omitted whole and counted, never silently truncated.

### Limits and delivery

One page is processed at a time. Limits are 3 redirects per page, 40 HTTP attempts including robots and failures, 15 seconds per request and 120 seconds for the runtime workflow. Source work stops with 20 seconds reserved for the append and receipts. SDK startup and shutdown also need the hosted platform's execution timeout, which remains a qualification gate. Redirects remain on the same exact host, never downgrade HTTPS, and pass the same public-address checks. DNS answers are checked and the connection is pinned to a checked address. The transport does not use environment proxies, cookies or retries.

Each response is limited to 2 MiB encoded and 2 MiB decoded, with 12 MiB aggregate limits for each. Application-read framing bytes count toward encoded usage; headers have a separate 16 KiB per-response cap. gzip and zlib-wrapped deflate are supported. Brotli, concatenated gzip members and trailing compressed data are rejected. Failed reads retain byte counts; decompression errors expose a known lower bound and conservative reserved upper bound. These are not TLS/socket-overhead measurements.

The static cap is 30,000 elements, 20 forms and 100 native controls per page, 500 controls per run, 100 options per control and 2,000 delivered options per run. Complete serialized field rows are at most 8 KiB; label and context accumulation is bounded during construction, before the final row check. The one dataset append is at most 512 KiB using the pinned SDK's serializer. Limits can omit rows; receipts identify the outcome.

When enabled on the live listing, the planned pay-per-event tariff is $0.001 per delivered complete control, including its labels and options, through the automatic dataset item event. At most 500 field events cost $0.50; the default 20-field input limits field events to $0.02. A supported positive platform event cap reduces selection to the number of whole controls it can fund. For zero source reads and delivery, set maxFields=0. Local SDK tests cover zero and fractional capacities, but the hosted API may ignore a literal zero or reject a cap below one event. Do not use maxTotalChargeUsd=0 as a no-spend guarantee. Check the live listing for the active price. Owner runs can be exempt from consumer event charges and still incur platform compute, storage and transfer costs. An event cap does not cap those platform costs.

The runtime makes at most one dataset append and never retries it automatically. Confirmed transport counts survive later SDK bookkeeping failures; uncertain delivery or charging stays unknown. A completed invocation can replay its receipt without fetching or appending. An incomplete or corrupt journal fails closed and requires inspection of the actual dataset and platform events. The Actor does not claim a safe automatic retry when it cannot prove one.

### Local package

Python 3.12, Apify SDK 4.0.0 and Apify client 3.2.0 are pinned with hashed dependencies. The dataset transport seam depends on those versions and needs review before an SDK upgrade. The package configures 256 MiB of memory. This is the allocation setting; actual peak memory and execution cost depend on the run.

Native association references: [form ownership](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms) and [labels](https://html.spec.whatwg.org/multipage/forms.html#the-label-element). These define the supported relationships; this prototype does not implement the full browser processing model.

# Actor input Schema

## `pageUrls` (type: `array`):

One to ten exact authorized HTTP(S) URLs, on default ports, without credentials or authentication parameters. Duplicates are fetched once. Omission uses the public HTTPBin pizza form demo; no action is followed.

## `formSelector` (type: `string`):

All native forms by default. Supported: form, #id, form#id, .class or form.class, with ASCII identifier spelling. Selecting a form still includes its externally owned controls. No pseudoclasses, state selectors or arbitrary CSS.

## `maxFields` (type: `integer`):

Zero to 500 complete field rows across the page list. Default 20. A lower available dataset-event budget reduces this cap. Options and labels are included in each field row.

## Actor input object example

```json
{
  "pageUrls": [
    "https://httpbin.org/forms/post"
  ],
  "formSelector": "form",
  "maxFields": 20
}
```

# Actor output Schema

## `fields` (type: `string`):

No description

## `outcomes` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pageUrls": [
        "https://httpbin.org/forms/post"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sapph1re/html-form-field-map").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "pageUrls": ["https://httpbin.org/forms/post"] }

# Run the Actor and wait for it to finish
run = client.actor("sapph1re/html-form-field-map").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pageUrls": [
    "https://httpbin.org/forms/post"
  ]
}' |
apify call sapph1re/html-form-field-map --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sapph1re/html-form-field-map"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yn43CWYNPwYbC63k5/builds/4jtzt89m06ytEaAQO/openapi.json
