OGC API Features vs WFS: a practical migration guide
WFS 2.0 is not broken. It delivers features reliably and it is deployed in thousands of production spatial data infrastructures. But it was designed in an era of SOAP and XML tooling, and today most of the clients that want your data speak JSON and expect a resource-oriented REST API. OGC API Features is the standards body's answer, and moving to it is usually additive rather than a rip-and-replace.
This guide is the migration we actually ran for a state SDI, with the parts that surprised us called out explicitly.
The shape of the difference
WFS exposes a small set of verbs — GetCapabilities, DescribeFeatureType, GetFeature — parameterised by a large and expressive query language. OGC API Features inverts that: it exposes many resources under clean URL paths, each doing one thing, described by an OpenAPI document any REST tool can consume.
WFS 2.0:
GET /wfs?service=WFS&version=2.0.0&request=GetFeature&typeNames=parcels&count=10
OGC API Features:
GET /collections/parcels/items?limit=10Capability negotiation moves to OpenAPI
The GetCapabilities document does not disappear so much as it is decomposed. The landing page at the API root links to /conformance (which conformance classes the server implements) and /collections (what is published). A client no longer parses one large XML capabilities blob; it follows links, and the machine-readable contract is the OpenAPI 3.0 description served at /api.
- GET / — landing page with links to conformance, collections, and the API description.
- GET /conformance — the conformance classes the server supports.
- GET /collections — the published feature collections and their metadata.
- GET /api — the OpenAPI document, the contract that replaces DescribeFeatureType.
Translating filters: OGC Filter to CQL2
This is where most of the migration effort lives. WFS carries an OGC Filter Encoding expression, usually as XML, sometimes as the older ECQL. OGC API Features uses CQL2, which has both a compact text form and a JSON form. The concepts map cleanly, but the syntax does not, so you need a translation layer rather than a search-and-replace.
-- WFS OGC Filter (abbreviated): tenure = 'freehold' AND area > 1000
-- becomes CQL2-Text:
tenure = 'freehold' AND area_sqm > 1000
-- spatial predicate, CQL2-Text:
S_INTERSECTS(geom, BBOX(138.5,-35.0,138.7,-34.8))Pagination: from startIndex to cursors
WFS pages with count and startIndex, an offset model that grows expensive and inconsistent on large, changing collections. OGC API Features standardises on limit plus a next link that the server generates. Treat that next link as opaque — do not reconstruct it — and your client stays correct even if the server switches from offset to a keyset cursor underneath.
The bounding-box CRS trap
The edge case that cost us the most time: coordinate order and CRS in the bbox parameter. OGC API Features defaults to CRS84, which is longitude-latitude order, whereas many WFS deployments and their clients assume EPSG:4326 in latitude-longitude order. A bbox that looks correct will silently return zero features if the axis order is flipped.
Be explicit. Send the bbox-crs parameter, confirm the collection advertises the CRS you are querying in, and add an assertion in your test suite that a known feature is returned for a known box. This single check would have caught the bug on day one instead of day three.
- Default bbox CRS is CRS84 (lon, lat) — not EPSG:4326 (lat, lon).
- Pass bbox-crs explicitly rather than relying on the default.
- Assert a known feature is returned for a known bounding box in CI.
See Airfree Geospatial in action
Enterprise cadastre, LiDAR, earth observation, and AI on sovereign spatial infrastructure. Book a demo tailored to your data and jurisdiction.
Request a demo →