Data Exchange (Import / Export)
One generic envelope and one registry give every entity — core or plugin — import and export over CLI, REST and the admin UI.

The envelope
Every export is a self-describing JSON envelope keyed by entity:
{
"vbwd_export": "shop_products",
"version": 1,
"shop_products": [ /* one row per record */ ]
}Identity travels by natural key (a slug or other stable field) and foreign keys are resolved by slug on import — so an export from one instance re-resolves cleanly on another. Imports are upserts.
The seam
Core owns an agnostic EntityExchanger contract and a registry. Each exchanger declares an entity_key, a natural_key, permissions and supported formats. Core iterates the registry; it never names a plugin entity.
Using it
# REST (require_admin)
POST /api/v1/admin/data-exchange/<key>/export
POST /api/v1/admin/data-exchange/<key>/import
# CLI
flask data-exchange export <key> out.json
flask data-exchange import <key> out.json
# fe-admin: Import / Export page, one control per entityExtending from a plugin
# plugins/<name>/.../data_exchange/<name>_exchangers.py
class MyExchanger(EntityExchanger):
entity_key = "my_things"
natural_key = "slug"
# export()/import_() …
# in the plugin's on_enable():
data_exchange_registry.register(MyExchanger(session))The CMS documentation pages you are reading are imported exactly this way: categories via the cms_terms exchanger, pages via the cms_post importer.