# django-rest-framework-gis: GeoJSON fields and serializers for Django REST Framework

> This library gives Django REST Framework the ability to read and write GeoJSON, through a GeometryField, a serializer field for computed geometries, and a set of serializers for GeoDjango models. It is a small, long-lived add-on with a wide compatibility matrix and an explicit performance trade-off in its rounding options.

**openwisp/django-rest-framework-gis** — Geographic add-ons for Django REST Framework. Maintained by the OpenWISP Project.

- Repository: https://github.com/openwisp/django-rest-framework-gis
- Website: http://openwisp.org
- Stars: 1,132 · Forks: 222
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/openwisp-django-rest-framework-gis

## The field that does the GeoJSON conversion

The central object in the library is the GeometryField, which is a subclass of the Django REST Framework WritableField. It handles GeoDjango geometry fields and provides custom to_native and from_native methods for GeoJSON input and output.

The name WritableField is significant in a DRF context. A regular serializer field is read-only unless it declares how to set a value, and for a geometry the value is not a scalar you assign but a GEOS object that has to be parsed from GeoJSON on the way in and serialised back on the way out. A WritableField is one that implements that write path, which is what makes a geometry field usable in a POST or PUT rather than only in a response.

The field takes four optional arguments, and each of them is a real design decision rather than a convenience.

precision passes coordinates through Python's builtin round() function, rounding values to the provided level of precision. The README's example is concrete: a Point with lat and lng of [51.0486, -114.0708] passed through a GeometryField(precision=2) returns a Point with a lat and lng of [51.05, -114.07].

remove_duplicates removes sequential duplicate coordinates from line and polygon geometries, and the README says this is particularly useful together with the precision argument, because the likelihood of duplicate coordinates increases as the precision of the coordinates is reduced. That is a precise statement of a real interaction: rounding a polygon down to fewer decimal places can collapse adjacent vertices onto each other, and a polygon with repeated points is a degenerate shape.

auto_bbox, if True, includes a bounding box in the GeoJSON object, which is the smallest possible rectangle enclosing the geometry, as defined in the GeoJSON specification.

transform defaults to None and can be set to any value accepted by GEOSGeometry.transform. The README says to set it to 4326 if your input geometries are in another projection and you want to produce output according to the GeoJSON standard, and that if it is None, or if the input geometries do not have a SRID, the output coordinates will not be transformed.

The README also flags the cost of the first two options directly. While precision and remove_duplicates are designed to reduce the byte size of the API response, they will also increase the processing time required to render the response, which will likely be negligible for small GeoJSON responses but may become an issue for large responses. There is a performance_tests.rst file at the top of the repository, which suggests the project takes that trade-off seriously enough to measure it.

## Automatic field mapping, and the serializer for computed geometries

There is a piece of behaviour here that has changed over the life of the library, and it is documented explicitly. As of 0.9.3 there is no need to define the field explicitly in your serializer, because it is mapped automatically during initialization in rest_framework_gis.apps.AppConfig.ready().

That is a significant convenience, because the alternative is that every model serializer with a geometry field has to declare the field explicitly, and forgetting to do so silently produces a serializer that returns the raw value in a different form rather than an error. The cost of the automatic mapping is that it happens in an AppConfig.ready() hook, which is application startup, and it means the mapping is not visible in your serializer source. The installation step reflects this, since the app has to be added to INSTALLED_APPS after rest_framework:

```python
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework_gis',
    # ...
]
```

The order matters, and the README says so explicitly, which is the kind of detail that is easy to lose.

The second field type is GeometrySerializerMethodField, a subclass of DRF's SerializerMethodField. It handles values that are computed with a serializer method and are used as a geo_field, so the point of it is a geometry that does not exist as a model field but is derived from one, and the derived value still has to come out as GeoJSON rather than as a Python object the JSON encoder cannot handle.

The README then moves on to a Serializers section, which begins with GeoModelSerial and covers the serializers for GeoDjango models.

The distinction between the two field types is worth keeping in mind when you design an API. GeometryField is for a geometry that is stored on the model. GeometrySerializerMethodField is for a geometry you compute, and the compute happens in your get_ method, and the field only handles the conversion.

## Compatibility, and how many rows of that table you actually need

The compatibility table in the README is long, and reading it tells you two things about the project.

The first is the range of Django and DRF versions the library has tracked. The current line, 1.3.x, supports DRF 3.12 up to 3.18, Django 4.2 to 6.1 and Python 3.10 to 3.13. The line below it, 1.2.x, supports DRF 3.12 to 3.17, Django 4.2 to 6.0 and Python 3.9 to 3.13. The table then continues back through 1.1.x, 1.0.x, 0.18.x, 0.17.x and on down to 0.2, with the oldest rows covering DRF 2.4.3 and Django 1.5.

So the project has been maintained across roughly a decade of Django releases and about a dozen library versions. That is a real signal about the maintenance posture, and the release history backs it up: 1.3.0 on 2026-09-01, 1.2.1 on 2026-05-04 and 1.2.0 on 2025-06-02, with the last push on 2026-10-03 and the repository not archived.

The second thing the table tells you is how to use it. You do not need the whole table, you need one row, the one matching your Django, your DRF and your Python. The setup.py states the current floor in machine-readable form, with django>=4.2, djangorestframework>=3.12,<3.19 and django-filter>=23.5,<27.0 as the install requirements. The django-filter dependency is worth noting because the package declares it alongside Django and DRF, with its own upper bound.

The row you need is also the one that tells you when to upgrade. If you are on Django 5.0 and want the 1.3.x line, you are within range. If you are on Django 6.1, you are within the 1.3.x range but outside the 1.2.x one, so the 1.2.x line has already dropped support for a Django version you might still be running.

The version in setup.py is obtained from the package itself, via get_version() imported from rest_framework_gis, and the CHANGES.rst file at the top of the repository is the release history.

## Testing against a real PostGIS, not a fake client

The docker-compose.yml and Dockerfile in the repository show how the project tests itself, and the answer is more interesting than a typical unit test setup.

The compose file defines two services. The test service builds from the repository's own Dockerfile, depends on the postgres service, and mounts the package, the tests and the README into the container. The postgres service uses the postgis/postgis:15-3.4-alpine image, which is PostGIS on PostgreSQL 15, and the test command is:

```yaml
command: sh -c "pip install -e . && python ./tests/manage.py test tests/django_restframework_gis_tests && ./run-qa-checks"
```

So the test suite runs against a real PostGIS server, with a named volume for the database data, and it runs the QA checks after the tests. The port is published on 5432 for the case where you want to connect to it yourself.

The Dockerfile is where the reason becomes obvious. The base is python:3.10-alpine, and the comments explain each group of packages. postgresql-client is required by psql, and postgresql-dev, musl-dev and gcc are required by psycopg. The last group is the interesting one: gdal-dev, geos-dev and proj-dev are required by geodjango, with a note that gdal-dev rather than gdal is what is actually needed, and a TODO about optimizing the installation by using --virtual.

That is the cost of this library made concrete. To test a GeoJSON field, you need a real spatial database, and to talk to a spatial database from Django you need GDAL, GEOS and PROJ, which are the native geospatial stack. A library that only formats strings would not need any of it.

There is also a performance_tests.rst file at the top level, which is the counterpart to the README's note that precision and remove_duplicates trade processing time for response size. The project measures that trade rather than leaving it as folklore.

## Conclusion

Adopt django-rest-framework-gis if you already use Django REST Framework with GeoDjango and want your API to speak GeoJSON without hand-writing the field plumbing, especially if you need bounding boxes or coordinate rounding. Do not adopt it if your API already returns GeoJSON by other means, or if you need projections beyond what the transform option covers, because the library is a field and serializer layer rather than a GIS toolkit. Verify first that your Django, DRF and Python versions fall inside the row of the compatibility table that matches the release you install, and that your spatial backend, PostGIS or SpatiaLite, is set up, because the library needs the database to do the geometry work.

## FAQ

### What does the GeometryField in django-rest-framework-gis do?

It is a subclass of the DRF WritableField that handles GeoDjango geometry fields with custom to_native and from_native methods for GeoJSON input and output. It accepts an optional precision, remove_duplicates, auto_bbox and transform argument.

### Which Django and DRF versions does django-rest-framework-gis 1.3.0 support?

The compatibility table lists 1.3.x as supporting DRF from 3.12 to 3.18, Django from 4.2 to 6.1 and Python from 3.10 to 3.13. The setup.py declares django>=4.2, djangorestframework>=3.12,<3.19 and django-filter>=23.5,<27.0.

### Do I have to declare GeometryField on my serializer?

No. The README notes that as of 0.9.3 there is no need to define the field explicitly in your serializer, because it is mapped automatically during initialization in rest_framework_gis.apps.AppConfig.ready(). The package must still be added to INSTALLED_APPS after rest_framework.

### What database does the django-rest-framework-gis test suite use?

The docker-compose.yml runs the tests against a postgis/postgis:15-3.4-alpine service, and the Dockerfile installs the native geospatial stack, with gdal-dev, geos-dev and proj-dev required by geodjango.

## Sources

- [License: MIT](https://github.com/openwisp/django-rest-framework-gis/blob/master/LICENSE)
- [openwisp/django-rest-framework-gis on GitHub](https://github.com/openwisp/django-rest-framework-gis)
- [Project website](http://openwisp.org)
- [README](https://github.com/openwisp/django-rest-framework-gis/blob/master/README.md)
- [Releases](https://github.com/openwisp/django-rest-framework-gis/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openwisp-django-rest-framework-gis
