What Is GeoJSON? The Definitive Guide

GeoJSON is a plain-JSON text format for geographic features, standardised as RFC 7946 in 2016, and its coordinates are always longitude first, then latitude, on WGS 84. Someone sent you a .geojson and it opens as a wall of numbers. Or you exported one from QGIS and it draws in the North Atlantic. Or a web map rejects it as "not valid GeoJSON" when every JSON linter says it is fine. Every command and number below was run on 15 September 2026 against one real dataset, IBGE's 2022 state boundaries of Brazil, and every claim about our viewer was measured on the live tool.

What is GeoJSON?

GeoJSON is a text format for geographic features, written as plain JSON and standardised as RFC 7946 in August 2016. A file is usually one FeatureCollection holding an array of Feature objects, and each Feature has a geometry (the shape) and a properties object (the attributes, any JSON you like). There is no schema, no sidecar file and no binary section, which is why every web map library speaks it natively.

The smallest useful file is a single feature, and this one is a real fixture from our lab: {"type":"Feature","properties":{"name":"Brasília"},"geometry":{"type":"Point","coordinates":[-47.8825,-15.7942]}}. Read the coordinates carefully, because this is the sentence people skip and then lose an afternoon to: a GeoJSON position is written longitude first, then latitude. Brasília is [-47.8825, -15.7942], not the -15.79, -47.88 you would type into a search box. The spec says [longitude, latitude]; a swapped pair parses without complaint and draws in the wrong hemisphere.

The seven geometry types, and the eighth that holds them

Every geometry has a type and a coordinates array; only the nesting depth changes.

  • Point — one position, [lon, lat].
  • MultiPoint — an array of positions.
  • LineString — two or more positions, in drawing order.
  • MultiLineString — an array of LineString coordinate arrays.
  • Polygon — an array of closed rings; the first is the outer boundary, the rest are holes.
  • MultiPolygon — an array of Polygon coordinate arrays. Brazil's 27 states are 12 Polygon and 15 MultiPolygon, because a coastal state with islands is several rings that are not holes in each other.
  • GeometryCollection — the eighth type: a geometries array of any of the above, with no coordinates of its own. It turns up where you did not ask for it, as we found below.

What RFC 7946 changed, and why your file may be older than the spec

Before 2016 the format was the 2008 "GeoJSON specification", a community document, and many exporters still write that flavour by default, GDAL included. RFC 7946 tightened five things, and each explains a file that "should work" and does not.

  • WGS 84 only, and the CRS member is gone. An RFC 7946 file is always longitude and latitude in decimal degrees on WGS 84. The 2008 spec allowed a top-level crs object naming any coordinate system; the RFC removed it. A file with a crs member is a 2008-spec file, and if it names anything but WGS 84 you have the problem in the next section.
  • The right-hand rule for rings. An outer ring runs counter-clockwise, holes clockwise. Shapefiles store exterior rings clockwise, so every polygon converted without the RFC flag is "backwards"; most readers do not care.
  • The bounding box got a fixed order. bbox already existed in 2008; the RFC pins its meaning to [west, south, east, north], still optional on any object.
  • Three dimensions, no more. A position is [lon, lat] or [lon, lat, elevation]; a fourth number should not be there; the RFC says SHOULD NOT, not MUST NOT.
  • Cut at the antimeridian. A shape crossing 180° longitude should be split in two. A recommendation for writers; readers vary.

What the flag actually did to a real file

We converted the same Shapefile twice, in the default 2008 mode and with -lco RFC7946=YES, and compared the outputs.

  • The CRS member disappeared. The 2008-mode file opens with "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } } on its fourth line; the RFC file goes straight from "name": "BR_UF_2022" to "features". Neither has a bbox, which is opt-in.
  • Coordinates were rounded to 7 decimals. Acre's first vertex went from [-68.792817347,-10.999569268] to [-68.7928173,-10.9995693]. The driver defaults to 15 decimals in 2008 mode and 7 in RFC mode; 7 decimals is about a centimetre.
  • Rings were rewound. Acre's outer ring, 41,498 vertices in both files, starts at the same vertex and walks the other way: clockwise in the 2008 file, counter-clockwise in the RFC file.
  • The file shrank by a quarter. 39,274,442 bytes became 29,108,343, almost entirely from the shorter numbers.
  • Three polygons changed type, silently. The 2008 file has 12 Polygon and 15 MultiPolygon; the RFC file has 11 Polygon, 13 MultiPolygon and three GeometryCollection features, for Alagoas, Paraná and Goiás. A second run with -lco RFC7946=YES -lco COORDINATE_PRECISION=15 gave back 12 and 15, so the cause is the rounding: at 7 decimals tiny rings in Alagoas and Goiás collapse to fewer than four distinct points and one Paraná ring crosses itself; GDAL's RFC mode repairs them with MakeValid(), and the collapsed parts come out as LineString and MultiLineString members packed with the polygon into a collection, 36 vertices lost. GDAL says nothing about it unless you run with --debug on, which prints one Running MakeValid() line per repaired state. The 2008 mode with -lco COORDINATE_PRECISION=7 takes a different route, SetPrecision(), and keeps 12 Polygon and 15 MultiPolygon. Precision is a geometry decision, and a validator run after conversion is cheap.

The classic wrong place: metres in a format that means degrees

The complaint reads "I exported my layer to GeoJSON and it is in the ocean" or "it is nowhere at all". The cause is almost always coordinates that are not [longitude, latitude] in degrees: swapped axes, or projected metres.

Our dataset ships in SIRGAS 2000 geographic coordinates, so to reproduce the bug we exported it to SIRGAS 2000 / UTM zone 23S: ogr2ogr -f GeoJSON utm.geojson BR_UF_2022.shp -t_srs EPSG:31983. GDAL does not refuse. It writes a 55,908,110-byte file, 1.4 times the 2008-mode degrees file (1.9 times the RFC file, which also rounds to 7 decimals), and Acre's first vertex becomes [-2171693.959168141, 8673426.088929612] where the correct file has [-68.7928173, -10.9995693]. The easting is seven digits and negative because Acre sits four UTM zones west of zone 23. GDAL also does what the 2008 spec allowed: it writes "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::31983" } } at the top, honest about the numbers but not GeoJSON in the RFC 7946 sense. GDAL honours that member on the way back in, ogrinfo reports the file as SIRGAS 2000 / UTM zone 23S, and QGIS reads vector files through GDAL, which is why such a file "works in QGIS and breaks on the web".

What happens next depends on the reader, so we measured ours. Our free GeoJSON viewer reads files with OpenLayers, which honours a crs member only when it names a coordinate system OpenLayers already knows: the WGS 84 aliases, including urn:ogc:def:crs:OGC:1.3:CRS84, and Web Mercator. EPSG:31983 is not on that list. Six fixtures, each dropped into the live tool:

  • A CRS naming EPSG:31983, in either spelling. The viewer shows no error. The layers panel says 27 features and 27 Polygon, everything looks loaded, and the exported GeoJSON puts Acre's first vertex at [-19.5086591263066, 61.20664023081724]: the North Atlantic between Iceland and Norway, about 9,000 km from Acre. The UTM metres were taken as Web Mercator metres and turned back into degrees. GDAL's own urn:ogc:def:crs:EPSG::31983 spelling gave the identical pair.
  • The same metres with no CRS at all. Still no error, still 27 features. With no crs the reader assumes degrees, so -2171693.96 is a longitude of two million degrees. The exported first vertex is [-2171693.959168141, -53.911070387188786]: the latitude wrapped through the Mercator formula to −53.9°, the longitude came back unchanged, and the shape is nowhere you can pan to. Two silent failures from one file: "Brazil off Iceland" and "nothing on any map".
  • A 2008-style file declaring CRS84. Draws correctly. Acre's first vertex exported as [-68.7928173712301, -10.999569659337354], the same pair the RFC file gave; the extra digits are the round trip through Web Mercator.
  • A CRS whose type OpenLayers does not recognise. Both the 2008 spec's "type":"link" form and a made-up "type":"bogus" stop the parse with the Error processing file. Make sure it is a valid GeoJSON file. banner. A malformed crs fails loudly; a well-formed crs naming the wrong system fails quietly. That asymmetry is the interoperability problem RFC 7946 §4 cites when it removes the member.

The fix is on the writer's side and it is one flag: add -t_srs EPSG:4326 to the export so the file contains degrees. Do not edit the crs line by hand; the numbers inside are still metres, and no declaration turns them into [longitude, latitude]. Reproject with ogr2ogr -t_srs EPSG:4326; in the default 2008 mode GDAL still writes a CRS84 declaration, which is harmless (the viewer honours it, as the third fixture above showed), and -lco RFC7946=YES drops it entirely.

Converting to GeoJSON with ogr2ogr

The command that produces a file every reader agrees on is ogr2ogr -f GeoJSON out.geojson in.shp -t_srs EPSG:4326 -lco RFC7946=YES. GDAL's GeoJSON driver page lists every layer-creation option; these four change the file in ways you can see.

  • RFC7946=YES — everything in the section on what the flag did to a real file: no crs, rings rewound, 7 decimals, and possibly a geometry type change where rounding invalidates a ring. The default is NO.
  • COORDINATE_PRECISION=6 — one decimal fewer than the RFC default. A millionth of a degree is about 0.11 m at the equator (111,320 m per degree, divided by a million), still below the accuracy of most vector data. It took our file from 29,108,343 bytes to 26,839,688, a 7.8 % saving, and Acre's first vertex became [-68.792817,-10.999569]. Five decimals is roughly a metre.
  • WRITE_BBOX=YES — adds a bbox to the collection and to every feature: our file gained 28 of them, [ -73.9904500, -33.7511780, -28.8476399, 5.2718411 ] at the top and one per state, for 1,626 extra bytes.
  • ID_FIELD=CD_UF — promotes an attribute to the feature-level "id" and removes it from properties. Our states came out as "id":"12", "id":"13", "id":"15", strings because the Shapefile field was text. Useful when a web map needs a stable key.

Three more things the lab settled.

  • Elevation survives, despite the warning. Converting a [-47.8825,-15.7942,1172] point with -dim XYZ prints Warning 1: Attempt to write Z geometries to layer pz that does not support them. Z component will be discarded, then writes [-47.8825,-15.7942,1172.0], which reads back as a 3D Point. Do not take the warning at its word.
  • GeoJSONSeq is the streaming form. ogr2ogr -f GeoJSONSeq out.geojsonl in.shp -t_srs EPSG:4326 writes one Feature per line with no wrapping collection: 27 lines, 7-decimal coordinates, no crs, 29,108,905 bytes. A line-oriented tool can stream it; a reader that expects one FeatureCollection cannot, because the whole file is not one JSON value.
  • TopoJSON is not GeoJSON. It encodes shared arcs once and is often far smaller, but its top-level object is a Topology. GDAL reads it and cannot write it: ogr2ogr -f TopoJSON answers ERROR 1: TopoJSON driver does not support data source creation. Our viewer rejects it two ways depending on the name: a .topojson never reaches the parser and gets Unsupported file type. Use GeoJSON (.geojson or .json).; the same bytes renamed .json reach the parser and get Error processing file. Make sure it is a valid GeoJSON file.

Size, measured

GeoJSON is big uncompressed and the smallest of the three common vector formats once zipped, because text deflates far better than binary shapes. These numbers are from our format-comparison lab of 15 September 2026 on the same dataset: IBGE's BR_UF_2022.zip, 13,717,460 bytes as downloaded, SHA-256 282ec7f0f0beeeead45e6609f4ffffce161bda04cb8ee0afcad2316d1c841bcb, 27 states, 1,138,650 vertices.

  • Shapefile — 18,227,115 bytes for the five files together, the .shp and its four sidecars; 13,794,222 bytes re-zipped at maximum compression.
  • GeoPackage — 18,386,944 bytes, slightly larger than the Shapefile because SQLite's pages and the spatial index cost more than the .shx they replace; 13,828,105 bytes zipped.
  • GeoJSON at the RFC default of 7 decimals — 29,108,343 bytes, the biggest on disk by a wide margin; 8,457,365 bytes zipped, the smallest of the three by about 5.1 MiB.
  • GeoJSON at 6 decimals — 26,839,688 bytes; 7,132,808 bytes zipped.

So on disk and in a browser's memory, GeoJSON costs 1.6 times what a Shapefile does; zipped, it costs 61 %, and the size argument against it mostly disappears. The full eight-criterion comparison, including browser load times, is in our Shapefile vs GeoPackage vs GeoJSON post.

How to open a GeoJSON file

You do not need a desktop GIS to look at one. Drop the file on our GeoJSON viewer and it draws the features on a base map, lists them in a layers panel with a count per geometry type, and opens a sortable attribute table. The step-by-step is in how to open a GeoJSON file online; the facts below were measured on the live tool with the files from this guide.

  • What it accepts. The file picker filters on extension, .geojson, .json, .kml and .kmz, but a dragged file skips that filter and is judged by its suffix in the parser: a GeoJSON renamed .txt gets Unsupported file type. Use GeoJSON (.geojson or .json). There is no size limit and no feature limit; the 56-megabyte UTM file above loaded in under two seconds, and the ceiling is your browser's memory, for which we will not invent a number.
  • One file per drop, ten per session. Dropping two files at once loads the first and ignores the second. After the tenth file the Add button in the layers panel simply disappears; there is no "too many files" message.
  • What the counts mean. The RFC 7946 file shows 27 features and 24 Polygon, because the three GeometryCollection states belong to no geometry group; the 2008-mode file shows 27 features and 27 Polygon. A collection is labelled Collection in the attribute table's Type column and in the feature panel, not in the layers row.
  • A bare geometry works. A file whose top level is a single Feature, or even a naked Point, loads as one feature; the naked point shows 0 fields.
  • Rings are not checked. A clockwise outer ring draws normally and exports still clockwise; the viewer neither validates nor repairs winding.
  • Styling in properties is applied and then hidden. The simplestyle keys, fill, fill-opacity, stroke, stroke-width, marker-color, marker-size, marker-symbol, style the map and are hidden from the attribute table: a feature with seven style keys plus name and plain lists 2 fields.
  • Lines and polygons are de-jittered by 20 cm. Every non-point geometry is simplified with a 0.2-metre tolerance before drawing and before export. A test line whose middle vertex sat 5 cm off the straight path lost it; one 1 m off kept all 3 vertices. For state boundaries that is 15,249 vertices out of 1,138,614, invisible at any zoom; where 5 cm matters, keep the original and treat the export as a picture.

As general advice for QGIS, not something this guide measured: drag the file onto the map canvas. If the layer properties show anything other than EPSG:4326, export it again with -t_srs EPSG:4326 before publishing.

Test files you can use

Every URL here was requested on 15 September 2026 with a browser user agent, recording status, Accept-Ranges and the CORS header. A list like this rots; read it as a snapshot of one day.

  • Natural Earth 1:110m countries, the file to start with. is 838,726 bytes, answers 206 with accept-ranges: bytes and access-control-allow-origin: *, and is small enough to download and drop. Licence: the repository's LICENSE.md opens with "Everything here is public domain."
  • Brazil's outline from IBGE's own API. returns GeoJSON directly: 65,655 bytes, one MultiPolygon feature, no crs member, read by GDAL as WGS 84. It answers 200 with access-control-allow-origin: * but no accept-ranges, so it is alive and open for a download-then-drop, not a range request. The response carries no licence text; it is IBGE's public data, so cite the source.
  • The 27 states we used. BR_UF_2022.zip from geoftp.ibge.gov.br, named in the size section, is a Shapefile; convert it with the command above and you have our exact 29,108,343-byte file.
  • One that is dead, so you stop looking. The OKFN data-portal copy of the world boundaries at data.okfn.org/data/datasets/geo-boundaries-world-110m/data/countries.geojson is still linked from older tutorials and its hostname no longer resolves. When a sample URL fails at DNS, the data usually moved to GitHub; search for the file name.

Where Geodocs fits

We build Geodocs, a platform for field data and GIS teams, and we wrote this guide because GeoJSON is the format our users hand us most often, and the crs member is what breaks most often.

Working with the other formats? Our complete guide to the Shapefile format covers the sidecar files and the .prj problem this guide's wrong-place section inherits; the GeoPackage guide is the one-file alternative; and the KML and KMZ guide covers the other text format the same viewer opens. To look at a file right now, the GeoJSON viewer linked above is free and runs in your browser.

Spotted an error, or a gotcha we missed? Tell us. We re-test this page rather than copy-pasting it.

Last verified: 15 September 2026.

Related Articles

Navegação