How to Export Data from Plone: Methods, API Limits & Portability
Complete guide to extracting data from Plone: REST API batching, collective.exportimport, plone.exportimport, .zexp, Data.fs — with version-specific recommendations and failure modes.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Plone has no single "Export Everything" button. Your export method depends on your Plone version, the content framework in use (Archetypes vs. Dexterity), and whether you're migrating to another Plone instance or leaving the ecosystem entirely.
Because Plone is built on Python and stores data in the Zope Object Database (ZODB) rather than a relational SQL database, you cannot run mysqldump or query business tables with SQL. Even when Plone uses PostgreSQL through RelStorage, the data is stored as pickled Python objects. The RelStorage FAQ confirms you cannot perform SQL queries against this data in the way most teams expect. To read it, you must wake up the Python objects within the Plone application context.
This architectural reality dictates every export method available to you. A migration-ready extract — content, blobs, users, permissions, relations, translations, redirects — typically requires combining more than one tool. A backup (copying Data.fs and blob storage) gives you a restorable copy of the same site, not an extract another platform can ingest.
TL;DR: Use plone.exportimport for full-site JSON exports on Plone 6.1+. Use collective.exportimport for cross-version migrations from Plone 4–6. Use plone.restapi for programmatic, paginated extraction or continuous sync. Raw database backups are restore artifacts, not migration-ready exports. Each method has coverage gaps.
Plone Export Methods Compared
| Method | Content | Users / Groups | Relations | Translations | Version History | Binary Files | Best For |
|---|---|---|---|---|---|---|---|
plone.restapi (@search) |
✅ (paginated JSON) | Via @users / @groups |
Separate calls | ❌ | ❌ | Separate download | Custom scripted extraction |
plone.exportimport (@export) |
✅ | ✅ | ✅ | ✅ | Opt-in (--include-revisions) |
✅ (in ZIP) | Plone 6.1+ site-to-site |
| collective.exportimport | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ (base64, blob paths, or download URLs) | Cross-version migration (Plone 4→6) |
| collective.jsonify | ✅ (one JSON per object) | Supplementary scripts | ❌ | ❌ | ❌ | ✅ (base64) | Legacy Plone 2.1–4.x export |
| Zope .zexp (ZMI) | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | Identical-version cloning only |
| Data.fs direct copy | ✅ (entire DB) | ✅ | ✅ | ✅ | ✅ | Blob storage separate | Full backup / same-version restore |
| Transmogrifier pipelines | ✅ | Configurable | Configurable | Configurable | ❌ | ✅ | Complex ETL pipelines |
plone.restapi: The Primary API Export Path
plone.restapi is the standard programmatic interface for extracting content from Plone. It is included in Plone 6 when you install the Plone package. Version 10 requires Python 3 and works with Plone 6.2, 6.1, 6.0, and 5.2. On Plone 5.x, it is available as an installable add-on.
The API uses content negotiation — set the Accept header to application/json to get JSON responses. Authentication supports both HTTP Basic and JWT tokens.
Authentication
plone.restapi includes a Plone PAS plugin for JWT authentication, installed automatically with the product. To obtain a token:
curl -X POST http://your-plone-site/Plone/@login \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"login": "admin", "password": "secret"}'The response includes a token field you use in subsequent requests via the Authorization: Bearer <token> header.
Batching and Pagination
Collection-like resources are batched if the result set exceeds the batch size. The default batch size is 25.
You control pagination with two query parameters:
b_start— Index of the first item in the batch (default: 0)b_size— Number of items per batch (default: 25)
The response includes a batching object with hypermedia links (first, last, prev, next) that your client can follow. The items_total field gives you the total result count.
Unlike SaaS platforms with strict vendor-imposed rate limits, Plone is self-hosted. Your throughput limits are dictated by your server's RAM and WSGI server configuration (Waitress, Gunicorn). But you must still paginate — requesting too many heavy objects at once causes memory spikes and timeouts.
There is no official b_size=-1 parameter to disable batching. Setting b_size to a very large number works but can cause memory issues on large sites. The safest approach is to iterate through pages using the next link in the batching response.
Extracting Content via @search
The @search endpoint maps to Plone's portal_catalog. You can query by content type, path, date ranges, and any indexed field:
curl "http://your-plone-site/Plone/@search?portal_type=Document&b_size=100" \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>"By default, @search returns catalog brain summaries — not full content. Adding ?fullobjects=true returns complete JSON representations, but Plone must wake each object from the ZODB, which is significantly slower. On a site with 100,000 items, fullobjects=true means 100,000 individual ZODB object loads; budget for this to take several hours on modest hardware.
Two details catch people regularly. First, @search on a container includes the container itself unless you constrain depth with path.depth. Second, SearchableText does not auto-append a trailing wildcard the way Plone's LiveSearch does — add the * yourself if you want prefix matching.
REST API Edge Cases
- Summaries ≠ full objects.
metadata_fields=_allreturns less thanfullobjects=true, and some fields requested as metadata can fail JSON conversion. fullobjects=truehas a real cost. For a site with 100,000 items, fetching each object individually means 100,000 ZODB object loads.- Folder GETs include children. Folderish content includes child
itemsautomatically; useinclude_items=falseto avoid unexpected payload expansion. - Files are always a second hop. Serialized file/image fields give you filename, MIME type, size, and a
downloadURL. You need a separate GET for the bytes. - User export supports CSV. The
@usersendpoint can return CSV as ofplone.restapi10.0.0rc1. - Groups are separate from users. The
@groupsendpoint defaults to 25 groups at a time unless you raise thelimitparameter. - Registry export is admin-only. Reading or writing
@registryrecords requirescmf.ManagePortal.
What the REST API Does Not Export
The REST API gives you content objects, users, groups, workflow states, and vocabularies. It does not provide a single-call export for:
- Relations between objects (requires separate per-object inspection)
- Content ordering (position in parent)
- Local roles / sharing settings (per-object
@sharingendpoint, not bulk-exportable) - Translation linkage (depends on
plone.app.multilingual; no dedicated bulk export endpoint) - Portal configuration (registry, control panel settings)
- Portlet assignments
- URL redirects
For migration-grade extraction, the REST API is a building block — not a complete solution.
Step-by-Step: REST API Extraction Script
For teams writing custom extraction scripts, here's the practical workflow:
1. Authenticate and get a token:
TOKEN=$(curl -s -X POST http://localhost:8080/Plone/@login \
-H "Content-Type: application/json" \
-d '{"login":"admin","password":"secret"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")2. Enumerate content types:
curl -s "http://localhost:8080/Plone/@types" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"3. Paginate through all content:
import requests
base = "http://localhost:8080/Plone"
headers = {"Accept": "application/json", "Authorization": f"Bearer {TOKEN}"}
def export_all_content(portal_type):
url = f"{base}/@search?portal_type={portal_type}&fullobjects=true&b_size=50"
all_items = []
while url:
resp = requests.get(url, headers=headers).json()
all_items.extend(resp.get("items", []))
url = resp.get("batching", {}).get("next")
return all_items4. Download binary files separately:
for item in items:
if "file" in item and item["file"]:
file_url = item["file"]["download"]
binary = requests.get(file_url, headers=headers).content
# Save to disk5. Export supplementary data:
curl -s "http://localhost:8080/Plone/@users" \
-H "Accept: application/json" -H "Authorization: Bearer $TOKEN"
curl -s "http://localhost:8080/Plone/@groups" \
-H "Accept: application/json" -H "Authorization: Bearer $TOKEN"plone.exportimport: The Plone 6.1+ Official Export
plone.exportimport provides support to export and import all kinds of data from and to Plone sites using an intermediate JSON format. Most features use plone.restapi to serialize and deserialize data under the hood. This is the closest thing Plone has to a full site export — covering content, principals, relations, translations, discussions, redirects, and (optionally) version history. (Plone docs)
Two Export Interfaces
Command-line (server-side access required):
plone-exporter instance/etc/zope.conf Plone /tmp/plone_data/
plone-exporter --include-revisions instance/etc/zope.conf Plone /tmp/plone_data/By default, revision history (older versions of each content item, stored in CMFEditions) is not exported. Add --include-revisions if you need it. On large sites with active editorial workflows, version history stored in CMFEditions can make your Data.fs 10–50× larger than the current content alone — exporting revisions substantially increases both export time and output size.
REST API endpoint:
curl -X POST \
-H "Authorization: Bearer <token>" \
https://plone.example.com/plone/++api++/@export \
-o export.zipThe @export endpoint requires the plone.exportimport.export permission, granted to Site Administrators by default. The response is a ZIP file containing all exported data. On sites with 50,000+ objects, this endpoint runs synchronously and is prone to browser and HTTP proxy timeouts — use the command-line plone-exporter for large sites.
Export Structure
The exported layout is filesystem-friendly and easy to inspect. Each content item gets its own subdirectory named by UID, containing serialized data and blob files:
content/
__metadata__.json
plone_site_root/
<UID>/
data.json
file/<filename>
principals.json
relations.json
translations.json
discussions.json
redirects.jsonThis structure makes it straightforward to diff, inspect, and transform compared to a raw database snapshot.
plone.exportimport targets current Plone and Python versions only. It does not support Plone 4/5 or handle Archetypes-to-Dexterity conversion. If you're on an older version, you need collective.exportimport instead.
collective.exportimport: The Cross-Version Migration Workhorse
This is the package the Plone community uses for major version migrations. It allows you to migrate from Plone 4 to 6, from Python 2 to 3, and from Archetypes to Dexterity in one step. Plone's own upgrade documentation recommends it for large and complex migrations.
What It Exports
Each data type has its own browser view:
/@@export_content— Content items (select by type or export all)/@@export_members— Users, groups, and role assignments/@@export_relations— Object relations/@@export_localroles— Per-object sharing/permissions/@@export_translations— Translation links/@@export_ordering— Item positions within folders/@@export_discussion— Comments/discussions
If you select "Save to file on server," the export saves JSON files to the <var> directory of your Plone instance. The import view looks for files under /var/instance/import.
Blob Handling Strategies
collective.exportimport gives you three strategies for binary files:
- Base64 encoding: Embeds the file directly in the JSON. Avoid for large sites — base64 inflates binary size by approximately 33% and loading all blobs into memory simultaneously causes severe memory bloat.
- Blob paths: Exports the absolute filesystem path to the blob. Plone's migration training recommends this option when you can access the old blob store directly. This is the most memory-efficient option.
- Download URLs: References the file via URL for later retrieval. Useful when the source site remains accessible during import.
Key Constraints
- Export format is JSON — one file per content type (e.g.,
Document.json,Image.json) - No version history — only current versions are exported
- Modified dates get reset during import. A separate
@@reset_datesview resets modification dates to the values in the exported JSON, since import-time operations update the modified date. The original is stored asobj.modification_date_migratedon each new object. - Requires server-side access — you must install the package and access the Plone instance directly
- Performance is significant. Per the project README, importing 5,000 Documents takes approximately 25 minutes with versioning enabled and approximately 7 minutes without. For a 50,000-document site, that translates to roughly 4–5 hours of import time with versioning. Rehearse with production-like data volumes before scheduling a maintenance window.
The Volto Block Problem
If you're exporting from Plone 6 with Volto (the modern React frontend), you'll hit a data mapping challenge that no export tool solves on its own.
In Classic Plone, rich text is stored as standard HTML in a RichTextValue field. Most target systems — WordPress, Contentful, SharePoint — can ingest HTML directly.
In Volto, content is not stored as HTML. It lives in the blocks and blocks_layout fields as Slate JSON. A simple bold paragraph looks like this in the export:
{
"@type": "slate",
"plaintext": "This is a paragraph.",
"value": [
{
"children": [
{ "text": "This is a " },
{ "text": "paragraph.", "strong": true }
],
"type": "p"
}
]
}No other CMS natively understands Plone's Volto block schema. Much like dealing with proprietary HubL code during a HubSpot CMS export, you will need middleware to parse the blocks JSON and convert it into either standard HTML or the specific block format of your target system. The mapping is non-trivial:
| Volto Block Type | WordPress Equivalent | Contentful Equivalent | Sanity Equivalent |
|---|---|---|---|
slate (rich text) |
core/paragraph block |
Rich Text field | Portable Text array |
image |
core/image block |
Asset reference | Image object |
video |
core/embed block |
No native equivalent | External URL field |
listing |
Dynamic query block (plugin) | No native equivalent | Custom query type |
toc (table of contents) |
generateblocks/table-of-contents |
No native equivalent | Custom component |
| Custom add-on blocks | Requires custom plugin | Requires custom field type | Requires custom schema |
The blocks_layout field stores the display order as an array of UUIDs referencing keys in the blocks object. Any conversion tool must respect this ordering and handle blocks it doesn't recognize gracefully (typically by falling back to the plaintext property if present, or skipping the block).
When migrating to a non-Plone target, exporting via plone.restapi with fullobjects=true gives you the most portable JSON serialization, including resolved field values rather than internal references. For Volto sites, this is the only way to get the full blocks payload — the catalog brain summaries returned by @search without fullobjects=true omit block content entirely.
collective.jsonify: Legacy Plone Export (2.1+)
For very old Plone installations where neither plone.restapi nor collective.exportimport can be installed, collective.jsonify is the fallback. Its only dependency is simplejson, and it can be installed on any Plone version as far back as Plone 2.1.
The export produces one JSON file per content object, executed via External Methods registered through the Zope Management Interface (ZMI). The output format is collective.transmogrifier-compatible, meaning each JSON file maps directly to a pipeline item that transmogrifier can process — it is not directly importable by WordPress, Drupal, or other non-Plone platforms without an additional transformation layer.
To migrate Plone 1, 2, or 3 to Plone 6, the recommended pipeline is collective.jsonify for export and collective.exportimport for import.
Limitations:
- No export of relations, translations, or local roles without supplementary scripts
- No web UI or progress indicator — export progress must be monitored via Zope logs
- Base64-encoded binaries (memory-intensive for large file collections)
- No version history export
- Output is
collective.transmogrifier-compatible JSON, not directly importable by non-Plone systems
Transmogrifier Pipelines: ETL for Plone
Transmogrifier is a pipeline-based ETL tool for Plone content migration. Pipelines are defined in .ini-like configuration files. Each pipeline consists of ordered blueprints — reusable processing components that each receive a content item dict, optionally transform it, and pass it to the next blueprint in the chain.
A minimal transmogrifier pipeline for exporting content to JSON looks like:
[transmogrifier]
pipeline =
source
exporter
saver
[source]
blueprint = collective.transmogrifier.sections.csvsource
filename = items.csv
[exporter]
blueprint = collective.transmogrifier.sections.logger
name = exporter
[saver]
blueprint = collective.transmogrifier.sections.jsonwriter
directory = /tmp/exportBlueprints can read from the ZODB directly, apply field transformations, filter by content type, rename attributes, or write to arbitrary output formats. This flexibility makes Transmogrifier suited for migrations that require content transformation during export — renaming fields, splitting content types, merging folders. The trade-off is a steep learning curve and the requirement for deep Plone engineering knowledge. For straightforward exports, collective.exportimport has largely replaced it.
Exporting Users, Groups, and Permissions
Plone handles authentication and authorization via the Pluggable Auth Service (PAS). Exporting content does not automatically export users or their permissions — these are separate data tracks.
- With
collective.exportimport, run@@export_membersfor users and groups, and@@export_localrolesfor per-object permissions. - With
plone.restapi, users come from@users, groups from@groups, and local roles from the per-object@sharingendpoint (not bulk-exportable in a single call). - With
plone.exportimport, users and groups are included in theprincipals.jsonoutput automatically.
Local roles deserve particular attention. Plone allows permissions at the folder or document level — granting a specific user "Editor" rights on a single subfolder, for example. The permission model uses the following built-in roles: Reader, Editor, Contributor, Reviewer, Site Administrator. Extracting the granular per-object permission matrix requires walking the full object tree and calling @sharing on each object, or using @@export_localroles from collective.exportimport.
If you're migrating to a system with a simpler permission model (Webflow, basic SharePoint sites, most headless CMS platforms), you'll need to flatten this architecture into something the target can represent — typically by mapping all local role assignments to a single flat permission tier.
Exporting to WordPress: Field Mapping Reference
"Plone to WordPress" is one of the most common migration queries. WordPress ingests content via the WordPress REST API or the WXR (WordPress eXtended RSS) XML format. Here is how Plone fields map:
| Plone Field | WordPress Equivalent | Notes |
|---|---|---|
title |
post_title |
Direct mapping |
description |
post_excerpt |
Direct mapping |
text (Classic HTML) |
post_content |
Direct mapping; HTML passthrough |
blocks (Volto JSON) |
post_content as Gutenberg blocks |
Requires conversion; no standard tool exists |
effective (publication date) |
post_date |
ISO 8601 format; convert timezone |
subjects (tags) |
tags |
Create terms first, then assign by ID |
creators |
post_author |
Must pre-create WP users; map by username |
review_state |
post_status |
Map published→publish, private→draft |
image (lead image) |
_thumbnail_id (featured image) |
Upload via media endpoint, then set meta |
| UID | Custom meta field (_plone_uid) |
Store for redirect resolution |
| Original path | Custom meta field (_plone_path) |
Required for redirect rule generation |
There is no off-the-shelf Plone-to-WordPress migration tool. The standard approach is: export via plone.restapi or collective.exportimport, write a Python or Node.js script to transform the JSON, and POST to the WordPress REST API or generate WXR XML for import.
Choosing the Right Export Method by Plone Version
| Plone Version | Best Export Tool | Notes |
|---|---|---|
| 2.1–3.x | collective.jsonify |
Only option; pair with collective.exportimport for import on target |
| 4.x (Archetypes) | collective.exportimport |
Handles AT→Dexterity conversion; install via buildout |
| 5.0–5.2 | collective.exportimport + plone.restapi |
REST API available as add-on on 5.x, core on 5.2 |
| 6.0 | collective.exportimport or plone.exportimport |
Volto frontend; REST API is core |
| 6.1+ | plone.exportimport |
Ships with Plone; @export endpoint available |
Decision by Scenario
| Scenario | Recommended Method | Why |
|---|---|---|
| Plone 6.1 → Plone 6.1 | plone.exportimport |
Official, complete, single ZIP |
| Plone 4 → Plone 6 | collective.exportimport |
Handles AT→Dexterity, Python 2→3 |
| Plone 2.x/3.x → anything | collective.jsonify + collective.exportimport |
Only viable path for ancient versions |
| Plone → WordPress | plone.restapi scripted export + custom transform |
Most portable JSON; requires field mapping script |
| Plone → Contentful/Sanity | plone.restapi scripted export + custom transform |
Export schema must map to Contentful/Sanity content model |
| Plone → Drupal | collective.exportimport JSON + Drupal Migrate API |
Drupal's migrate framework can consume structured JSON |
| Full backup / disaster recovery | Data.fs + blob storage copy |
Complete, but version-locked |
| Complex content transformation | Transmogrifier pipeline | Maximum flexibility, highest effort |
ZEXP and Data.fs: Backup Methods, Not Export Methods
ZEXP
Zope's native .zexp format is available through the Zope Management Interface (ZMI). Navigate to the object, click "Import/Export," and download the file.
ZEXP files are Python pickle data. Both source and target Zope instances must be identical — same Zope version, same Products installed, same Python version. Even minor version differences can cause unpickling failures that are difficult to diagnose. Do not use .zexp for cross-version migrations or export to non-Plone systems.
Data.fs Direct Copy
Every Plone site stores its content in the ZODB, typically as Data.fs in var/filestorage/, with blob storage in var/blobstorage/. These are maintained synchronously; if they drift out of sync, you encounter POSKeyError errors when content is read.
This is a backup method, not an export method. The files are only usable by an identical or closely compatible Plone installation.
Data.fs copy is useful for: moving between identical servers, performing in-place upgrades within the same major version line, or creating a staging copy to run collective.exportimport against before the actual migration window.
Do not hand a Data.fs or RelStorage snapshot to the target-system team and call it the export. Use backup tooling when the question is "How do we restore this Plone site?" Use export tooling when the question is "How do we transform this data for another system?" Those are different jobs, and mixing them up creates bad migration plans.
Common Failure Modes
Here are the failure patterns that appear most frequently in Plone export and migration projects:
-
Blob storage not included in backup.
Data.fsdoesn't contain large files if blob storage is enabled (standard since Plone 4). You must copyvar/blobstorage/separately and keep the two in sync. AData.fswithout its matching blob store producesPOSKeyErroron any content with file attachments. -
Archetypes content on Plone 6. If you skipped the AT→Dexterity migration, your content types may not serialize correctly via
plone.restapi. Run the AT→Dexterity migration step first usingplone.app.contenttypes, or usecollective.exportimportwhich handles the conversion inline. -
Large sites timing out. The
@exportendpoint and@@export_contentviews run synchronously. On sites with 50,000+ objects, browser and reverse-proxy timeouts are common. Use the command-lineplone-exporteror the "Save to file on server" option incollective.exportimport. -
Revision history bloat. Plone stores content versions in
CMFEditions. These are not exported by default and can makeData.fsfiles 10–50× larger than the actual current content. If you export with--include-revisions, budget for substantially longer export and import times. -
Missing add-on content types. If your site uses add-on types (
PloneFormGen,eea.facetednavigation,collective.dexteritytextindexer-dependent types), the export tools may not serialize their custom fields correctly without explicit serializer adapters registered for those types. -
Encoding issues on Python 2. Plone 4 sites running Python 2.7 can produce JSON with mixed byte strings and unicode.
collective.exportimporthandles this internally, but custom scripts that concatenate or process the raw output may break withUnicodeDecodeError. -
Form data schema drift. KU Leuven's Plone 6 migration found that saved form data failed when stored rows no longer matched the current form structure. They kept the old Plone 4 site available for at least one year so users could view and download legacy data. (KU Leuven migration notes)
-
Modified dates silently change. Import-time operations update modified dates. Run
@@reset_datesafter import if date integrity matters for audit trails or publication history. -
Volto blocks with no
plaintextfallback. Some custom Volto block types store noplaintextrepresentation. If your conversion script relies onplaintextas a fallback for unrecognized block types, those blocks will silently produce empty content in the target. Audit custom block types before export. -
portal_catalogout of sync. On old or poorly maintained Plone sites, the catalog may not reflect actual content (objects present in ZODB but not indexed, or catalog entries pointing to deleted objects). Run a full catalog rebuild before export to avoid gaps between@searchresults and actual content.
Pre-Migration Export Checklist
Because Plone splits content, blobs, roles, redirects, and configuration across different tracks, your runbook should do the same.
- Inventory by object class, not just page count. Count custom types, files/images, forms, multilingual branches, comments, and redirects. Query
portal_catalogbyportal_typeto get per-type counts. - Choose the export path by Plone version. See the version table above.
- Decide blob strategy early. REST download, per-item files on disk, base64 in JSON, or legacy blob-path access all have different performance and validation costs. Base64 is generally unsuitable for sites with more than a few hundred MB of binary content.
- Export secondary objects deliberately. Do not assume users, groups, local roles, aliases, translations, relations, or version history come along "for free." Each requires an explicit export step.
- Preserve stable identifiers. Keep
UID, original path, and published URL history wherever the target can store them. These are essential for generating redirect rules. - Rebuild the catalog before export. Ensures
@searchresults match actual content. Run through ZMI →portal_catalog→ "Advanced" → "Clear and Rebuild." - Validate with counts and spot checks. Compare object counts by type, file counts, revision expectations, and redirect behavior between source and target.
- Test Volto block conversion separately. If the source is a Volto site, extract a representative sample of each custom block type and verify your conversion produces correct output before running full export.
- Rehearse with production-like data. Run at least one full dry run to measure timing, fix edge cases, and document rollback steps. Keep the old site available long enough for delta checks.
Getting the Data Out Is the Easy Part
Plone migrations are among the more technically demanding data extraction jobs. The combination of ZODB storage, version-dependent serialization, the Archetypes-to-Dexterity transition, and Volto's block format means there is no "one-click" path — especially when the target isn't another Plone instance.
The hard part is rarely getting bytes out of Plone. As is true when extracting data from other complex enterprise platforms, the hard part is rebuilding structure in the target: binaries, roles, redirects, translations, and content relationships — without losing trust in the data. The Volto block schema, the local roles matrix, and add-on content type fields are the three areas most likely to require custom engineering regardless of which export tool you choose.
Frequently Asked Questions
- Can I export Plone data using a SQL database dump?
- No. Even if Plone uses PostgreSQL via RelStorage, the data is stored as serialized Python pickles. The RelStorage FAQ confirms you cannot perform useful SQL queries against this data. You must extract data through Plone's Python environment using tools like collective.exportimport, plone.exportimport, or plone.restapi.
- How do I export all content from a Plone site?
- On Plone 6.1+, use the plone.exportimport @export endpoint or plone-exporter CLI to get a ZIP of all content, users, relations, and translations. On Plone 4–5, install collective.exportimport and use the @@export_content browser view. For Plone 2.x–3.x, use collective.jsonify (one JSON file per object) and pair it with collective.exportimport for import on the target.
- Does the Plone REST API have rate limits?
- Plone is self-hosted, so there are no vendor-imposed rate limits. However, plone.restapi paginates results with a default batch size of 25 items (b_size parameter). Performance is bound by ZODB I/O — using fullobjects=true on large result sets can be very slow as each object must be woken from the database.
- What is the difference between collective.exportimport and plone.exportimport?
- collective.exportimport supports Plone 4–6, handles Archetypes-to-Dexterity conversion, and works with Python 2 and 3. plone.exportimport ships with Plone 6.1+, focuses only on latest Plone/Python, and provides a REST API @export endpoint. Use collective.exportimport for cross-version migrations and plone.exportimport for Plone 6.1+ exports.
- What format does Plone 6 Volto export rich text in?
- If you are using Plone 6 with the Volto frontend, rich text is stored as Slate JSON blocks in the blocks and blocks_layout fields, not standard HTML. No other CMS natively reads this format — you will need to write middleware to convert the block JSON into HTML or your target system's block schema.