# django-import-export: CSV round trips with a diff preview in the Django admin

> A Jazzband package that turns any model into an import and export screen, built on tablib, with natural keys for moving data between environments and hooks for anonymising it on the way out.

**django-import-export/django-import-export** — Django application and library for importing and exporting data with admin integration.

- Repository: https://github.com/django-import-export/django-import-export
- Website: https://django-import-export.readthedocs.org/en/latest/
- Stars: 3,335 · Forks: 841
- Language: Python
- License: BSD-2-Clause
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/django-import-export-django-import-export

## What you get the moment you register a ModelAdmin

The pitch in the README is that this is an application and library for importing and exporting from a variety of sources, csv, xlsx, json and whatever else tablib supports. The reason it feels more than a library is the admin integration: register an `ImportExportModelAdmin` instead of `ModelAdmin` and the import and export screens appear on that model.

What arrives with those screens is a preview step. Data is uploaded, parsed, matched against existing rows, and shown as a diff before it is committed. That preview is the single most valuable feature in the package, because the alternative, writing an import that reads a CSV and calls `save()` per row, has no point at which a human can look at what the file will do.

The feature list also includes bulk import, CRUD and skip operations, foreign key handling, many-to-many support, validation of imported data, selectable items and fields at export time, export of a single instance from the change form, dark mode support and internationalization. Several of those are small things that only matter once real users touch the screens, which is a reasonable summary of what this package is for.

Authorization uses Django's own permission system, with separate import and export permission codes. That is the right design for a package that adds data entry to an admin, because a deployment that only wants read-only exports can grant exactly that.

## Three runtime dependencies and an exact pin on one of them

The `pyproject.toml` lists three runtime dependencies: `diff-match-patch==20241021`, `Django>=5.2` and `tablib>=3.7.0`. The exact pin on diff-match-patch is worth pausing on, because an exact version pin in a runtime dependency is unusual and it constrains your own resolution when other packages in the same environment need a different release.

What diff-match-patch is doing here is the diff display. Generating a character-level diff of what changed in a cell is precisely the job of that library, and pinning it suggests the project found a version where the output looked right.

tablib is the format layer, and the optional dependency extras are simply tablib extras passed through: `all`, `cli`, `ods`, `pandas`, `xls`, `xlsx` and `yaml`. That arrangement is elegant in the sense that format support is tablib's to provide, so this package does not need its own adapters for every spreadsheet library. The cost is that a user who wants xlsx support has to understand which tablib extra pulls in which library.

Supported Python versions run from 3.10 through 3.14 and the Django classifiers list 5.2, 6.0 and 6.1. The database support claim in the README is MySQL, PostgreSQL and SQLite, and the test extras confirm that intent: `psycopg[binary]` and `mysqlclient` both appear in the test requirements.

Build metadata uses setuptools with setuptools-scm, so versions come from tags. Licence is BSD-2-Clause, and the pyproject records the licence as a file reference rather than an SPDX string, which is the older style.

## Natural keys and hooks, the two features worth knowing by name

Natural keys are the feature that turns this from a spreadsheet convenience into a deployment tool. Exporting by primary key produces a file that only makes sense in the database it came from. Exporting by natural key produces a file that means the same thing in another environment, which is what you need when the target has different primary key sequences, a fresh staging database, or production data that has to reach development without its identity column.

The README lists this as a named use case rather than a feature bullet: creating portable data to transfer between environments. The same list includes safely updating project reference data by importing from version controlled CSV, and managing user access by importing externally version controlled auth user lists. Those are all the same idea applied to different data, and they are the use cases that justify a library over a shell script.

Hooks are the second. They let you add logic to the data on the way out, and the README's example is anonymising data on export. For a project that has to share production extracts with an external party, being able to strip or transform columns in one place, rather than post-processing the file, is worth more than it sounds.

Advanced usage goes further still: custom transformations for exported data, importing and exporting the same model instance as different views by defining separate resources, and adding dynamic filtering to the import and export forms. That last one turns the admin screen into something closer to a small reporting tool.

## Running the tests and the tools around them

The Makefile is a good map of how the project is developed. It defines a single test command and then several targets that call it:

```bash
pip install .
pip install .[tests]
coverage run tests/manage.py test core
```

The coverage target runs the test suite, combines the data files and prints a report. There is also a parallel test target, a lint target through flake8, and a clean target that removes build artifacts, byte-compiled files and test caches. Documentation is built through Sphinx, whose dependencies are pinned exactly in the docs extra.

A detail worth noticing: the test invocation uses Django's own test runner with warnings turned into errors, and there is a separate `runtests.sh` in the tree. Turning warnings into errors is a strict choice, and it is the kind of strictness that keeps a library from accumulating deprecation debt across Django releases.

The repository also contains `CLAUDE.md`, which is an unusual file to find in an open source project tree and suggests the maintainers keep project conventions written down somewhere an assistant can read them. There is a `SECURITY.md` as well, so security reports have somewhere to go.

The README claims 100 percent test coverage. For a package whose behaviour is mostly data transformation across many input shapes, that claim is the kind of thing to take at face value and verify for the paths you depend on, since coverage measures lines executed rather than behaviours exercised.

## Release cadence and the current version line

The three most recent releases are patch-level work on the 4.4 line: 4.4.1 on 2026-05-05, 4.4.0 on 2026-01-10, and 4.3.14 on 2025-11-13. Each release body is a single link to the changelog section on the documentation site, which is an efficient choice and also means the repository itself does not tell you what changed.

The gap between 4.4.0 and 4.4.1 is four months, and between 4.4.1 and today is a few more. That is a sane patch cadence for a package where most changes are Django compatibility and tablib format fixes, and it is consistent with a project that has 27 open issues rather than hundreds, which is a low number for a package this widely installed.

The version 4 series itself is the interesting thing. A major bump from 3 to 4 is a signal that something changed in the resource and admin API, and anyone upgrading from the 3.x line should read the changelog rather than assume compatibility. The README's mention of customisable admin forms and multiple resources per model suggests the 4 series formalised an approach that was previously ad hoc.

The default branch is `main` and the repository is not archived, with the last push on 2026-09-18. Documentation lives at django-import-export.readthedocs.io, and the README is mostly links into it: admin integration, bulk import, advanced usage, Celery integration, testing with Docker, and the API reference.

## Where this is the wrong approach

Three cases sit outside what this package handles well, and the first is simply scale.

Very large files are the boundary. Bulk import is supported, but this is a synchronous, request-scoped operation built around an admin form and a preview step. A hundred thousand rows with foreign key resolution and a diff preview is a long HTTP request, and the sensible path for that data is a management command using the library API, or a Celery task, which the project documents as an option. Even then, the per-row overhead is Python rather than a bulk database operation.

The second is data that arrives continuously through an API or a message queue. This package is a file-shaped tool. If the inbound data is JSON from a partner endpoint, you have gained a diff view and lost the natural fit.

The third is anything that is not file import or export, and this is worth saying plainly because the name invites confusion. It does not synchronise data between Django and another application. The natural keys feature produces portable output; it does not keep two systems in step, and there is no conflict resolution because there is no continuous process.

There is also the security consideration that comes with any package that writes to the database from an uploaded file. The permission codes restrict who can import, and validation of imported data is supported, but an import that matches existing rows and updates them is still a bulk write path with the consequences that implies. The README's use case of importing auth user lists from version control is a good example of that being done deliberately and auditable, which is the reason to prefer version controlled CSV over an emailed spreadsheet in the first place.

## Conclusion

This is the package to reach for when data enters or leaves a Django project through files and someone needs a UI to do it, because the alternative is writing the same form, the same mapping and the same error handling by hand. It handles the cases that break naive implementations: foreign keys, many-to-many relations, encodings, natural keys for moving between environments, and a preview diff before anything is written. Licence is BSD-2-Clause and the classifiers already cover Django 6.1. Register an `ImportExportModelAdmin` on one model first, confirm the export columns match what you expect, and only then automate the import through the API or Celery, since a preview nobody reads is where imports go wrong.

## FAQ

### How do I add import and export buttons to a Django admin model?

Register the model with `ImportExportModelAdmin` instead of `ModelAdmin`, and the import and export screens appear automatically. The screens include a preview step, so you can see the diff against existing rows before anything is written. Django permission codes control who is allowed to import and who is allowed to export.

### Which file formats can django-import-export read and write?

Whatever tablib supports, which the README lists as csv, json, xlsx, pandas, HTML and YAML. In practice you install the matching extra, so `pip install .[xlsx]` or `pip install .[yaml]`, since format support lives in tablib rather than in this package.

### How do I export data that can be imported into another environment?

Export using natural keys rather than primary keys, so the file identifies rows by a stable business key and remains valid in a database with different id sequences. The README lists portable data for transferring between environments as a named use case, and versions from version controlled CSV as another.

## Sources

- [django-import-export/django-import-export on GitHub](https://github.com/django-import-export/django-import-export)
- [License: BSD-2-Clause](https://github.com/django-import-export/django-import-export/blob/main/LICENSE)
- [Project website](https://django-import-export.readthedocs.org/en/latest/)
- [README](https://github.com/django-import-export/django-import-export/blob/main/README.md)
- [Releases](https://github.com/django-import-export/django-import-export/releases)

---

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