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 WKWebViewThe 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:
<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:
<input name="firstName" /> <!-- → firstName -->
<input id="card_number" /> <!-- → ccNumber -->
<input placeholder="Email" /> <!-- → email -->3. Type Attribute
Fallback for type="email" and type="tel":
<input type="email" /> <!-- → email -->
<input type="tel" /> <!-- → phoneNumber -->4. Label Text
Associated <label> content is matched as a last resort:
<label for="f1">City</label>
<input id="f1" /> <!-- → postalSuburb -->Two-Pass Scan
Detection runs in two passes:
- Pass 1 — Scan all elements for
autocompletematches (high confidence) - 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 (postalState → billingState) 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:
| Priority | Source | Selector |
|---|---|---|
| 1 | JSON-LD | <script type="application/ld+json"> |
| 2 | Microdata | [itemtype*="schema.org/Product"] |
| 3 | Open Graph | <meta property="og:type" content="product"> |
| 4 | DOM Heuristics | Cart 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:
- Structured data (JSON-LD, microdata, OG tags)
- Currency symbols in price strings (
$,£,€,¥) - 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:
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 → focusoutAll events bubble and are cancelable, matching browser behavior.
Select Matching
<select> elements are filled using a three-tier strategy:
- Exact value match (
option.value) - Exact text match (
option.textContent) - 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.