Polyfence is in Early Access — APIs and limits may still change. Your feedback shapes what we build.

Skip to content
Defined once
{
  "id": "zn_7c4a1f",
  "name": "Felixstowe · Yard 8",
  "type": "polygon",
  "polygon": [ { "lat": 51.95303, "lng": 1.3253 }, …5 ]
}
Area14.3 ha
Perimeter1.76 km
Payload261 B
Precision1e-6°
Flutter SDKpf_load_zones()GET /v1/zones/device-config
IDLEawaiting a crossing--:--:--
pf-esp32c3-9d2ae0
mobileevent.type
devicepf_get_events()
server{"type": …}
One zone. Three runtimes. Same tick.

evaluating
Drag a corner past the device

The geofence layer

One geofence layer.
Everywhere your product runs.

Same zones run on your mobile app, your IoT device, and your server. Define once. Evaluate anywhere.

  • Flutter
  • React Native
  • Embedded C
  • REST API
  • CLI

Open hardware · MIT licensed core · Free during Early Access

What we keep

A crossing tells you which device, which zone, which way and when. Never the route in between.

Every event lands as one row. Nothing else is written — we check the location that triggered it, then throw it away, unless you switch retention on.

Your event log — every row we keep
EventZone / deviceRecorded

Written to your account

device_idzone_idzone_nameevent_typeevent_timestamp

device_id is a name you give the device — our firmware uses a board slug and three bytes of the chip’s MAC. Either way we never learn who is holding it.

Evaluated, then dropped

latlngaccuracy_m

Position retention is off by default. We see a device id. You see your customer.

What the cap limits

iOS gives your app twenty geofences. Not twenty per library — twenty.

The operating system’s geofence budget is a wake-up service, not the detector. Polyfence hands it the nearest few and evaluates the rest itself, in your process, on every fix.

One app, one hundred zones — each cell is one zoneoutlined = a slot the OS holds  ·  dashed = taken back on reboot  ·  solid = evaluated in your process
iOS — region monitoringCLLocationManager · CLCircularRegion20of 100 · survives

The default is the ceiling — there is no headroom to hand out. Which twenty is your problem, and it changes as the user moves.

Android — region monitoringPlay Services · GeofencingClient100 → 0after a reboot

These are registrations, not memory. Play Services drops every one of them on reboot.

Polyfence — on-device evaluationGeofenceEngine · every fixall of themevery fix

Every zone, on every fix. Nothing was handed out, so nothing can be taken back.

Polyfence does register OS fences — up to twenty on iOS, fifty by default on Android — but only as a wake source, only while your app is backgrounded, and only if you switch it on. Detection never depends on them, which is why the Polyfence figures are the only ones unchanged across the break.

What it costs to watch

Nothing here polls on a timer. Still is two minutes. Driving is five seconds.

“Saves battery” is a promise you cannot check. The sampling interval is not — it is a table of constants in the shipped config, and it moves by 24× depending on what the phone thinks you are doing.

One minute of wall clock — each mark is one GPS fixupdateStrategy · intelligent
0:000:150:300:451:00
Moving, within 500 m of a zone boundarynearZoneUpdateIntervalMs
5 s12 fixes
Moving, further than 500 m outmovingUpdateIntervalMs
10 s6 fixes
Moving, further than 500 m out, battery 20 % or underlowBatteryUpdateIntervalMs
30 s2 fixes
Stationary — no 50 m move in five minutesstationaryUpdateIntervalMs
next at 2:00 →
2 min1 fix
Further than 500 m out, battery 10 % or underpauseOnCriticalBattery
pausednone
Inside 500 m and moving, proximity wins outright — neither the low-battery interval nor the critical-battery pause applies that close to a boundary. Played once at 12× real time.

Until you ask for it, the strategy is continuous — a fix every ten seconds wherever you are and whatever you are doing. Every interval above is a default the library fills in.

What it takes to start

From an empty file to your first event.

Flutter, React Native, embedded C, or REST. Four complete programs — every line that runs, and no line that doesn’t.

Flutterpubspec.yaml
flutter pub add polyfence
12345678910111213
import 'package:polyfence/polyfence.dart';
await Polyfence.instance.initialize();
await Polyfence.instance.requestPermissions();
await Polyfence.instance.addZone(Zone.circle(
  id: 'depot-north',
  name: 'Depot North',
  center: PolyfenceLocation(latitude: 51.95724, longitude: 1.30962),
  radius: 230,
));
Polyfence.instance.onZoneEnter.listen((event) {
  print('Entered: ${event.zoneName}');
});
await Polyfence.instance.startTracking();
README §Getting Started 1–513 lines
Same circle in all 4: 51.95724, 1.30962 · r 230 mCounts measured from the panels, not asserted
There is a CLI too. npm i -g polyfence-cli lists, creates and exports zones from a terminal. It manages your account rather than shipping inside your product, so it answers a different question than the four above.

What it’s for

The verb never changes.

The thing moving is yours. The line is yours. What happens when they meet is the part we do.

A vancrossesa depot gateand dispatch knows.Fleet & delivery

A collarcrossesa paddock lineand the farm knows.Livestock & agriculture

A tagcrossesa fence lineand the site knows.Perimeter security

A carcrossesa low-emission zoneand the driver knows.Compliance zones

A shoppercrossesa shop doorwayand the shop knows.Retail, tourism & events

A kidcrossesthe school gateand a parent knows.Family & personal safety

six sentences · one verbone row in your event log, whichever line is yoursall six, and what each would take to build →

Prefer to skip the code? Flash a reference board — WiFi, USB, cellular or LoRa. Browse reference boards →

Define once. Evaluate anywhere.

Free during Early Access. No credit card.