Your data appears in the ocean. Your polygon won't render in Mapbox. PostGIS rejects the file with an unhelpful error. Your area calculation is off by 20%. These are all GeoJSON problems with known, fixable causes. This is the list of every common mistake, what goes wrong, and the exact fix.
GeoJSON uses [longitude, latitude] order — longitude first. Every other human convention says "latitude, longitude" (GPS readouts, Google Maps links, geography class). The GeoJSON spec deliberately chose longitude-first to match the mathematical convention of (x, y) where x is the east-west axis.
The symptom: your data appears somewhere in the ocean, or at 0°N, 0°E (the Gulf of Guinea). A coordinate like [51.5, -0.12] is London as latitude/longitude — but as a GeoJSON position (longitude/latitude) it places a point at 51.5°E, 0.12°S, which is in Somalia. Even worse, if both values are in valid range for both interpretations, you get no error and a silently wrong map.
"coordinates": [51.5074, -0.1278] // This is NOT London
"coordinates": [-0.1278, 51.5074] // London: lon, lat
If you're generating GeoJSON from a database, check the column order. PostGIS ST_AsGeoJSON() outputs lon/lat correctly. Python's json.dumps({"coordinates": [row.lat, row.lon]}) doesn't — you need [row.lon, row.lat].
A GeoJSON Polygon ring must have the same coordinate as its first and last position. RFC 7946 is explicit: "the first and last positions are equivalent". This is different from some other formats (Shapefile, WKT) that close rings implicitly.
The symptom: PostGIS returns "Polygon is not closed", QGIS imports the polygon with a missing edge, or Mapbox silently drops the feature.
"coordinates": [[[2.293, 48.855], [2.300, 48.857], [2.302, 48.852], [2.295, 48.850]]] // First ≠ last — this is an open ring
"coordinates": [[[2.293, 48.855], [2.300, 48.857], [2.302, 48.852], [2.295, 48.850], [2.293, 48.855]]] // Last position repeats the first — ring is closed
Note that the closing position must be numerically identical — not just approximately equal. Even a floating-point rounding difference will fail strict validators.
RFC 7946 requires polygon rings to have at least 4 positions. Because rings are closed, the minimum meaningful shape is a triangle — 3 unique positions plus the repeated closing position = 4 entries. A ring with only 3 positions (even closed) is degenerate.
"coordinates": [[[2.3, 48.8], [2.4, 48.9], [2.3, 48.8]]] // 3 entries, last=first, but spec requires ≥ 4
"coordinates": [[[2.3, 48.8], [2.4, 48.9], [2.5, 48.8], [2.3, 48.8]]] // 4 entries: 3 unique vertices + closing repeat
Similarly, a LineString must have at least 2 positions. A LineString with a single point is not a valid line.
A Feature must have a "properties" key. Its value can be a JSON object or null — but the key must be present. Omitting it is technically invalid. Some parsers add default empty properties; others throw.
{"type": "Feature", "geometry": {"type": "Point", "coordinates": [2.35, 48.86]}}
{"type": "Feature", "properties": null, "geometry": {"type": "Point", "coordinates": [2.35, 48.86]}}
If you have properties to store, make properties an object. If you have no properties but want to be explicit, use null. Either way, the key must be there.
Like properties, a Feature must have a "geometry" key. It can be null (for features with no geometry, like administrative records), but the key must be present. Omitting it is invalid; null is valid and explicit.
{"type": "Feature", "properties": {"name": "Paris"}}
{"type": "Feature", "properties": {"name": "Paris"}, "geometry": null}
Most tools will silently skip features with missing geometry, but strict validators and some APIs will reject them outright.
The pre-2016 GeoJSON specification had a "crs" member that let you specify a custom coordinate reference system. RFC 7946 removed it. GeoJSON is now always WGS 84 (EPSG:4326), full stop. Tools that still write it include older QGIS versions, some ESRI exports, and custom pipelines written before 2016.
{
"type": "FeatureCollection",
"crs": {"type": "name", "properties": {"name": "EPSG:4326"}},
"features": [...]
}
{
"type": "FeatureCollection",
"features": [...]
}
The crs member won't break most tools — they'll just ignore it. But it signals that your data pipeline hasn't been updated to RFC 7946, and some strict validators and APIs will reject it. Remove it.
If your data is genuinely in a non-WGS 84 CRS (e.g. British National Grid), you must reproject it to WGS 84 before writing GeoJSON. GeoJSON cannot represent other CRS — that's the trade-off for simplicity. Use GDAL (ogr2ogr -t_srs EPSG:4326) or QGIS "Save As" to reproject before export.
Each geometry type requires a specific nesting depth in its coordinates array. Getting this wrong is common when constructing GeoJSON programmatically or converting from another format.
[lon, lat] — a single position, no outer array[[lon,lat], [lon,lat]] — array of positions[[[lon,lat], ...]] — array of rings/lines[[[[lon,lat], ...]]] — array of polygons, each an array of rings{"type": "Point", "coordinates": [[2.35, 48.86]]} // Extra nesting — this is a LineString shape, not a Point
{"type": "Point", "coordinates": [2.35, 48.86]}
GeoJSON is always WGS 84. If your source data is in British National Grid (Eastings like 530000, Northings like 180000), UTM (X around 500,000m), or any other projected CRS, you cannot write those values into a GeoJSON file and call it GeoJSON. The file will appear structurally valid but every coordinate will be in the wrong place.
The symptom: coordinates look plausible (no out-of-range errors), but your data loads somewhere completely wrong. OSGB coordinates fed to a web map as if they were WGS 84 will place points in the middle of Africa or the Arctic.
# GDAL command line ogr2ogr -t_srs EPSG:4326 -f GeoJSON output.geojson input.shp # Python with geopandas gdf.to_crs("EPSG:4326").to_file("output.geojson", driver="GeoJSON")
QGIS: right-click the layer → Export → Save Features As → set CRS to EPSG:4326 → save as GeoJSON.
Many APIs and tools expect a FeatureCollection even if you only have one feature. A bare Feature is valid GeoJSON but rejected by tools that only accept FeatureCollection as the top-level type — including Mapbox dataset uploads, some PostGIS import functions, and various GIS APIs.
{"type": "Feature", "properties": {"name": "Paris"}, "geometry": {...}}
{
"type": "FeatureCollection",
"features": [
{"type": "Feature", "properties": {"name": "Paris"}, "geometry": {...}}
]
}
When in doubt, always wrap in a FeatureCollection. There is no downside to wrapping a single feature.
The optional bbox (bounding box) member must be an array of exactly 4 numbers for 2D: [min_lon, min_lat, max_lon, max_lat]. Common mistakes include using 2-element arrays, putting latitude before longitude, or using 3 values instead of 4.
"bbox": [48.8, 2.3, 48.9, 2.4] // lat-first order "bbox": [[2.3, 48.8], [2.4, 48.9]] // wrong structure
"bbox": [2.3, 48.8, 2.4, 48.9] // [minLon, minLat, maxLon, maxLat]
A 3D bbox has 6 values: [minLon, minLat, minAlt, maxLon, maxLat, maxAlt]. The bbox is optional — if you're not sure about the correct values, omit it entirely rather than writing incorrect bounds.
The fastest way to catch all of these: run your GeoJSON through a validator before deploying. A 30-second check saves hours of debugging why your map is broken in production.
Paste any GeoJSON and get instant feedback on every issue above — with exact JSON path context and fix suggestions.
Open GeoJSON Validator