How AppFlowy Implements Data Import from Notion, Todoist, and Other Tools
AppFlowy implements data import through a cross-layer pipeline where the Flutter UI packages raw files into protobuf-encoded requests and hands them to a Rust backend that parses the data and creates workspace views, with specific handlers for Notion ZIP archives and extensible support for CSV-based tools like Todoist.
AppFlowy’s import architecture separates data ingestion from data processing to keep the codebase modular and extensible. According to the AppFlowy-IO/AppFlowy source code, the Flutter frontend never parses third-party formats directly; instead, it builds ImportItemPayloadPB or ImportZipPB objects and dispatches them via the FolderEvent API to Rust handlers that perform the heavy lifting.
Architectural Overview of the Import Pipeline
The import system spans four distinct layers, each with specific responsibilities and well-defined interfaces:
- Flutter UI Layer – Displays the import dialog, handles file selection, and constructs protobuf payloads. Key components include
ImportPanel,ImportTypeenum, andNotionImporterlocated infrontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/import/import_panel.dart. - Flutter Service Layer – Sends protobuf requests to the backend via the
FolderEventAPI. TheImportBackendServiceclass infrontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dartprovidesimportPages()andimportZipFiles()methods. - Protobuf Definitions – Define the wire format for all import payloads. The
ImportItemPayloadPB,ImportZipPB, andImportTypePBstructs live infrontend/rust-lib/flowy-folder/src/entities/import.rs. - Rust Backend Layer – Parses payloads, reads files or raw bytes, and converts data into internal
ImportItemstructs. Theimport_pages_handlerandimport_zip_file_handlerfunctions infrontend/rust-lib/flowy-folder/src/event_handler.rsreceive these events and delegate toFolderManager.import()andFolderManager.import_zip_file()for insertion into the workspace hierarchy.
This separation ensures that adding a new third-party source requires only a new UI entry and potentially a new parser in Rust, without touching the core folder management logic.
How Notion Data Import Works
Notion imports rely on the platform’s ZIP export format, which contains JSON page definitions and associated assets.
ZIP File Processing Pipeline
When a user initiates a Notion import:
- The
NotionImporterwidget in_sidebar_import_notion.dartcaptures the ZIP file path selected by the user. - The UI constructs an
ImportZipPBprotobuf message with thefile_pathfield set to the selected archive. ImportBackendService.importZipFiles()dispatches theFolderEventImportZipFileevent to the Rust backend.- The
import_zip_file_handlerinevent_handler.rsreceives the payload and callsFolderManager.import_zip_file(&data.file_path). - The Rust backend extracts the archive, traverses the Notion JSON structure, and transforms each page into an
ImportItemwith appropriate view layouts and metadata. - Each
ImportItemis persisted as aViewinside the user’s workspace. Errors are wrapped inFlowyErrorand propagated back to the UI for display.
Key Source Files for Notion Import
- UI Widget:
_sidebar_import_notion.dart– Handles file picker interaction and error display. - Service Interface:
import_service.dart– DefinesImportBackendService.importZipFiles(). - Protobuf Schema:
import.rs– ContainsImportZipPBand related type definitions. - Event Handler:
event_handler.rs– Implementsimport_zip_file_handlerand routes to the folder manager.
Extending AppFlowy for Todoist Data Import
As of the current main branch, AppFlowy does not ship a built-in Todoist importer, but the existing CSV pipeline provides a foundation for implementing one with minimal changes:
- UI Extension – Add a
todoistvariant to theImportTypeenum inimport_type.dartand register a corresponding entry inImportPanel. - Payload Construction – Todoist exports data as CSV. The UI can reuse the existing CSV path by creating an
ImportItemPayloadPBwithimportTypeset toImportTypePB.CSVand encoding the file bytes. - Backend Parsing – Extend the Rust
ImportItemconversion logic in the folder manager to detect the Todoist CSV schema, mapping rows to checklist items and creating views withTodoListblock layouts.
Because FolderEventImportData and import_pages_handler already handle CSV parsing, no new event handlers are required for basic Todoist support; only the schema-specific transformation logic needs implementation.
Importing from Other Tools: CSV, Markdown, and AFDatabase
The generic import panel in import_panel.dart supports several additional sources through the same ImportBackendService.importPages endpoint, varying only the ImportItemPayloadPB.import_type field:
- History Document (
.afdoc) – Migrates legacy AppFlowy documents viaImportTypePB.HistoryDocument. - History Database (
.afdb) – Imports archived database files asImportTypePB.HistoryDatabase. - Markdown and Text (
.md,.txt) – Converts Markdown content to AppFlowy documents usingImportTypePB.Markdown. - CSV (
.csv) – Creates grid views from tabular data viaImportTypePB.CSV. - AFDatabase (
.afdb) – Imports native AppFlowy database exports.
For each type, the UI reads the file as a string or byte array, constructs the appropriate ImportItemPayloadPB, and sends it through the FolderEventImportData channel to the import_pages_handler in Rust.
Practical Implementation: Code Examples
Triggering a Notion Import Programmatically
To initiate a Notion import from Dart code without using the default UI:
final notionZipPath = '/path/to/notion-export.zip';
final result = await ImportBackendService.importZipFiles([
ImportZipPB()..filePath = notionZipPath,
]);
result.fold(
(_) => debugPrint('Notion import succeeded'),
(err) => debugPrint('Notion import failed: ${err.msg}'),
);
This call creates an ImportZipPB, transmits it to the Rust backend, and returns a FlowyResult containing either void success or a FlowyError with diagnostic details.
Extending the Import Type Enum for Todoist
To add Todoist support, modify the Dart layer as follows:
// In import_type.dart
enum ImportType {
// existing entries...
todoist,
List<String> get allowedExtensions {
switch (this) {
case ImportType.todoist:
return ['csv'];
// other cases...
}
}
}
// In import_panel.dart
case ImportType.todoist:
final data = await File(path).readAsString();
importValues.add(
ImportItemPayloadPB.create()
..name = name
..data = utf8.encode(data)
..viewLayout = ViewLayoutPB.Grid
..importType = ImportTypePB.CSV,
);
break;
Because the Rust backend already handles ImportTypePB.CSV, this implementation immediately enables basic Todoist CSV ingestion. For richer metadata mapping (e.g., labels to tags), extend ImportTypePB in import.rs and add corresponding transformation logic in the folder manager.
Summary
- Notion imports utilize a specialized ZIP pipeline:
ImportZipPB→FolderEventImportZipFile→import_zip_file_handler→FolderManager.import_zip_file, which extracts JSON and assets to create workspace views. - Todoist and similar tools can leverage the existing CSV infrastructure by adding UI variants and reusing
ImportItemPayloadPBwithImportTypePB.CSV, minimizing backend changes. - Generic imports for Markdown, CSV, and AppFlowy-specific formats flow through
ImportItemPayloadPB→FolderEventImportData→import_pages_handler→FolderManager.import. - The architecture’s separation of concerns allows new third-party integrations by adding protobuf enum values and Rust parsing logic without modifying core UI components.
Frequently Asked Questions
Does AppFlowy support native Todoist imports?
No, AppFlowy does not currently ship with a built-in Todoist importer. However, the existing CSV import pipeline in import_service.dart and the generic ImportItemPayloadPB structure provide the necessary infrastructure to add Todoist support by extending the ImportType enum and handling Todoist's CSV export format in the Rust backend.
What file formats can AppFlowy import from other tools?
AppFlowy supports importing Notion (ZIP archives), Markdown (.md, .txt), CSV (.csv), History Documents (.afdoc), and History Databases (.afdb). Each format maps to a specific ImportTypePB enum value defined in frontend/rust-lib/flowy-folder/src/entities/import.rs, with the backend containing dedicated logic for extracting content from each file type.
How does the Rust backend handle Notion's ZIP structure?
The import_zip_file_handler in event_handler.rs receives the ZIP file path, delegates extraction to FolderManager.import_zip_file(), and walks the nested JSON files to construct ImportItem structs. These structs capture page hierarchies, content blocks, and embedded assets, which are then persisted as native AppFlowy View objects with appropriate layout metadata.
Can imports be triggered programmatically without the UI?
Yes, developers can call ImportBackendService.importPages() or ImportBackendService.importZipFiles() directly from Dart code, passing constructed ImportItemPayloadPB or ImportZipPB objects. This bypasses the ImportPanel UI and allows automated migration scripts or third-party integrations to ingest data into AppFlowy workspaces.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →