Skip to content

How It Works

Architecture

SnapFill is built as a core JavaScript engine with thin platform adapters:

packages/
├── core/src/                  # @snapfill/core — JS engine
│   ├── detectors/
│   │   ├── formDetector       # Field detection + classification
│   │   ├── cartDetector       # Shopping cart extraction
│   │   └── valueCapture       # Form value monitoring
│   ├── fillers/
│   │   └── formFiller         # Field filling with native setters
│   ├── injectable             # WebView-injectable script strings
│   ├── constants              # Regex patterns, autocomplete maps
│   └── types                  # TypeScript type definitions

├── react-native/              # React Native WebView adapter
├── android/                   # Kotlin library for Android WebView
└── ios/                       # Swift package for WKWebView

The core engine exports both functions for direct web use (tree-shakeable) and script strings for WebView injection. Native libraries inject the same scripts via platform-specific WebView APIs.

Form Detection

Fields are classified using four signal types, applied in priority order:

1. Autocomplete Attribute (Highest Confidence)

HTML5 standard autocomplete values are mapped directly:

html
<input autocomplete="given-name" />  <!-- → firstName -->
<input autocomplete="cc-number" />   <!-- → ccNumber -->
<input autocomplete="billing address-line1" /> <!-- → billingAddressLine1 -->

2. Name / ID / Placeholder Regex

Heuristic pattern matching against element attributes:

html
<input name="firstName" />     <!-- → firstName -->
<input id="card_number" />     <!-- → ccNumber -->
<input placeholder="Email" />  <!-- → email -->

3. Type Attribute

Fallback for type="email" and type="tel":

html
<input type="email" />  <!-- → email -->
<input type="tel" />    <!-- → phoneNumber -->

4. Label Text

Associated <label> content is matched as a last resort:

html
<label for="f1">City</label>
<input id="f1" />  <!-- → postalSuburb -->

Two-Pass Scan

Detection runs in two passes:

  1. Pass 1 — Scan all elements for autocomplete matches (high confidence)
  2. Pass 2 — Scan remaining elements using regex, type, and label signals

This ensures autocomplete-tagged elements always win when both signals are present.

Billing Context

Address fields are automatically remapped to billing equivalents (postalStatebillingState) when the element is inside a billing container — detected by walking up to 5 parent elements checking class names, IDs, and <legend> text.

Cart Detection

Shopping cart data is extracted using four strategies in priority order:

PrioritySourceSelector
1JSON-LD<script type="application/ld+json">
2Microdata[itemtype*="schema.org/Product"]
3Open Graph<meta property="og:type" content="product">
4DOM HeuristicsCart container patterns + price regex

The first source that returns results is used. Each returns structured product data with names, prices (in cents), quantities, and totals.

Currency Detection

Currency is inferred from:

  1. Structured data (JSON-LD, microdata, OG tags)
  2. Currency symbols in price strings ($, £, , ¥)
  3. Domain TLD (.co.uk → GBP, .com.au → AUD)

Form Filling

Native Property Setters

Modern frameworks (React, Vue, Angular) override the standard input.value setter. Setting value directly doesn't trigger framework state updates. SnapFill uses native property descriptors to bypass this:

ts
const nativeSetter = Object.getOwnPropertyDescriptor(
  HTMLInputElement.prototype, 'value'
)?.set;

nativeSetter.call(element, value);

Event Dispatch

Events are dispatched in the correct order to mimic real user interaction:

focus → focusin → [value set] → input → change → blur → focusout

All events bubble and are cancelable, matching browser behavior.

Select Matching

<select> elements are filled using a three-tier strategy:

  1. Exact value match (option.value)
  2. Exact text match (option.textContent)
  3. Partial text match (contains)

Live Detection

The injected scripts use a MutationObserver to detect DOM changes and re-scan for fields automatically. This handles:

  • SPAs that render forms dynamically
  • Multi-step checkout flows
  • Lazy-loaded form sections

Scans are debounced (300–500ms) and deduplicated — messages are only posted when the detected field set actually changes.

Released under the MIT License.