Apple announced URL filtering for Network Extension at WWDC25 (session 234) and shipped it with iOS 26. It's the first API that lets a regular App Store app filter URLs system-wide on an unmanaged iPhone: every browser, every app, full URLs rather than domains. We built a complete NEURLFilter implementation for Pledgely, client and server, so this is how the machine actually fits together, including the parts the documentation spreads across a dozen pages.
The problem NEURLFilter solves
Before iOS 26, your options for content filtering on iPhone were all compromised. Safari content blockers only cover Safari. DNS profiles filter by domain and are trivially removable. NEFilterDataProvider sees everything but only runs on supervised devices, so it's closed to consumer apps. Screen Time's Family Controls can shield whole categories but gives an app no say over individual URLs.
NEURLFilter closes that gap with an unusual design: the URL check happens on-device, but the blocklist lives on your server, and Apple's architecture makes it cryptographically impossible for you to learn what URLs the user visits. That last part is not marketing. It's enforced by the protocol.
The three moving parts
1. The control provider. Your app embeds an ExtensionKit extension implementing NEURLFilterControlProvider. It never sees URLs; the filtering itself runs inside the OS. Its real job, beyond start()/stop(reason:), is answering fetchPrefilter(existingPrefilterTag:) (see part 2). Your app configures the system through NEURLFilterManager: set pirServerURL, pirPrivacyPassIssuerURL, pirAuthenticationToken, and your control provider's bundle identifier, then saveToPreferences(). Status moves through stopped → starting → running, and you observe it the way you'd observe any NE manager.
2. The prefilter. A compact representation of your blocklist: a Bloom filter, delivered as raw bit-vector data plus its parameters (bit count, hash count, murmur seed) wrapped in an NEURLFilterPrefilter. The OS periodically asks your control provider for it (prefilterFetchInterval sets the cadence), passing the tag of the version it already has so you can answer "unchanged" by returning nil; ours tags with a SHA256 of the bit vector. Where the data comes from is your call inside that callback: fetch it from your backend, or ship it in the app bundle. Every URL the device loads is checked against the filter locally. Bloom filters produce false positives by design, and that's the point: a hit doesn't mean "blocked", it means "possibly blocked, ask the server". The overwhelming majority of URLs miss the prefilter and load with zero network overhead. The filter also performs sub-URL generation ("fuzzy matching" in Apple's docs), so one entry covers variants like a leading www, path fragments, and query strings; your blocklist doesn't need to enumerate them.
3. The PIR lookup. On a prefilter hit, the OS queries your server using Private Information Retrieval, a homomorphic-encryption protocol where the server answers "is this URL on the list?" without ever learning which URL was asked about. Apple first shipped this PIR stack for Live Caller ID Lookup in iOS 18; NEURLFilter reuses the same server protocol. The request is authenticated with a Privacy Pass token (so you can gate the service to your subscribers) that is unlinkable to any user account, and in production it arrives through an Oblivious HTTP relay, so your server doesn't even see the client's IP.
Stack all three and you get the property that makes this API remarkable: you maintain the blocklist, you pay for the servers, and you still cannot build a browsing log. For a category where users are rightly paranoid about surveillance (accountability apps have earned that paranoia), this is the best privacy architecture any platform has offered.
Details that will save you a day each
- Fail-open or fail-closed is your call.
shouldFailCloseddecides what happens when the filter can't reach your server. For an adult-content blocker, fail-closed is the honest choice; a blocker that fails open is a blocker with a built-in bypass. - OHTTP only applies to distribution builds. Xcode installs talk to your PIR server directly; TestFlight and App Store builds route through the OHTTP relay. Test both paths, because a gateway misconfiguration is invisible in development and fatal in production.
- The prefilter and the PIR database must be built from the same snapshot. Regenerating both is one pipeline step, not two; a prefilter that's newer than the database produces lookups that miss, which in a fail-closed filter surfaces as user-visible weirdness.
- Wire
resetPIRCache()andrefreshPIRParameters()to a debug menu on day one. When your PIR server restarts, devices can hold stale tokens and parameters; the symptom is the filter showing enabled while status sits at stopped, withNEVPNConnectionErrorDomainPlugin code 7in Console. Those two calls are the fix, and you don't want to discover that without a button for them. - You can't curl your own PIR endpoints. Requests require Privacy Pass tokens that only iOS can produce, so your monitoring has to probe adjacent surfaces (the token-key endpoint, gateway health) rather than the query path itself.
The entitlement to plan around
Everything above compiles against a stock SDK. None of it runs without com.apple.developer.networking.networkextension containing url-filter-provider, a managed entitlement Apple grants case by case, baked into your distribution provisioning profile once approved. Reasonably so: a URL filter provider sits in the load path of every URL on the device, and a fail-closed one can break browsing outright, so vetting who ships one makes sense. Plan for the request process as part of your schedule. Ours is built, deployed server-side, and parked while our request is in review.
The server half is its own project, and Apple's example code gets you maybe two thirds of the way. Running Apple's PIR service in production covers the rest.
Pledgely today ships its iOS blocker on Screen Time with the NEURLFilter stack ready to swap in, because the product question (what happens when you turn the blocker off?) matters more than the transport. If you're evaluating blockers as a user rather than a developer, start with the honest iPhone comparison.
Put real stakes behind quitting
Pledgely blocks porn across your whole phone and charges your own pledge only if you turn the blocker off. Stay clean, pay nothing.