Skip to main content

Migration: v3 to v4

Description — Migrating from Couchbase Lite for Dart v3 to v4
Related Content — Install | Vector Search

Overview​

Version 4 replaces the multi-package architecture with a single cbl package powered by Dart native assets. Native libraries are now downloaded and compiled automatically by the Dart build system — no manual setup or separate platform packages are needed.

Package changes​

v3 Packagev4 Replacement
cblcbl (unchanged)
cbl_dartcbl (merged)
cbl_fluttercbl (merged)
cbl_flutter_platform_interfaceRemoved (no longer needed)
cbl_flutter_ceRemoved (edition configured via pubspec.yaml)
cbl_flutter_eeRemoved (edition configured via pubspec.yaml)
cbl_libcblite_apiRemoved (handled by build hook)
cbl_libcblitedart_apiRemoved (handled by build hook)

Dependency changes​

Before (v3) — Flutter​

dependencies:
cbl: ...
cbl_flutter: ...
cbl_flutter_ce: ... # or cbl_flutter_ee

Before (v3) — Standalone Dart​

dependencies:
cbl: ...
cbl_dart: ...

After (v4) — Both Flutter and Standalone Dart​

dependencies:
cbl: ^4.0.0-dev.6

A single dependency is all that is needed for both Flutter and standalone Dart apps.

Edition configuration​

In v3, the edition was selected by choosing a package (cbl_flutter_ce vs cbl_flutter_ee) or by passing it to CouchbaseLiteDart.init().

In v4, configure the edition in your package pubspec.yaml:

hooks:
user_defines:
cbl:
edition: enterprise # or community (default)

If you are using a Dart pub workspace, hooks.user_defines are read from the workspace root pubspec.yaml, so put this configuration there instead.

If no edition is specified, the Community edition is used by default.

Initialization changes​

Before (v3) — Flutter​

import 'package:cbl_flutter/cbl_flutter.dart';

await CouchbaseLiteFlutter.init();

Before (v3) — Standalone Dart​

import 'package:cbl_dart/cbl_dart.dart';

await CouchbaseLiteDart.init(edition: Edition.enterprise);

After (v4)​

Couchbase Lite no longer requires explicit initialization before use.

In v4, Database.defaultDirectory automatically resolves to the platform's standard app data directory for Flutter apps (see Default database directory for the per-platform locations). For standalone Dart processes, the current working directory is used.

If you previously passed filesDir to CouchbaseLite.init to customize the default database directory, use Database.defaultDirectory instead.

import 'package:cbl/cbl.dart';

Database.defaultDirectory = '/path/to/files';

Vector search initialization​

In v3, vector search was automatically enabled during CouchbaseLite.init if the extension was available and the system supported it. The init methods accepted an autoEnableVectorSearch parameter to control this behavior.

In v4, vector search is never enabled automatically. autoEnableVectorSearch has been removed. You must explicitly call Extension.enableVectorSearch before opening a database that uses vector search:

final status = Extension.enableVectorSearch();
if (status != VectorSearchStatus.enabled) {
// Handle the case where vector search could not be enabled.
}

Extension.enableVectorSearch now returns a VectorSearchStatus enum instead of throwing an exception when vector search is unavailable. See Working with Vector Search for details on the status values.

Vector search configuration​

In v3, vector search setup was manual. In v4, enable it in pubspec.yaml:

hooks:
user_defines:
cbl:
edition: enterprise
vector_search: true

Vector search requires the Enterprise edition and is supported on ARM64 and x86-64.

FullTextIndex.langauge renamed to language​

The langauge method on FullTextIndex was misspelled. It has been renamed to language:

// Before (v3)
final index = FullTextIndex([FullTextIndexItem.property('text')])
.langauge(FullTextLanguage.english);

// After (v4)
final index = FullTextIndex([FullTextIndexItem.property('text')])
.language(FullTextLanguage.english);

pinnedServerCertificate and trustedRootCertificates type changes​

The pinnedServerCertificate and trustedRootCertificates fields on ReplicatorConfiguration have changed types from Uint8List? to CryptoData? and PemData? respectively.

// Before (v3)
final config = ReplicatorConfiguration(
target: urlEndpoint,
pinnedServerCertificate: certBytes, // Uint8List
trustedRootCertificates: pemBytes, // Uint8List
);

// After (v4)
final config = ReplicatorConfiguration(
target: urlEndpoint,
pinnedServerCertificate: DerData(certBytes), // or PemData(certString)
trustedRootCertificates: PemData(pemString),
);

MutableDocument constructor changes​

The MutableDocument() and MutableDocument.withId() constructors have been merged into a single constructor where id is now a named parameter:

// Before (v3)
final doc1 = MutableDocument();
final doc2 = MutableDocument({'key': 'value'});
final doc3 = MutableDocument.withId('my-id');
final doc4 = MutableDocument.withId('my-id', {'key': 'value'});

// After (v4)
final doc1 = MutableDocument({});
final doc2 = MutableDocument({'key': 'value'});
final doc3 = MutableDocument(id: 'my-id', {});
final doc4 = MutableDocument(id: 'my-id', {'key': 'value'});

The data parameter is now required and non-nullable. For an empty document, pass {}. The MutableDocument.withId constructor has been removed. When providing an id, the convention is to pass it before the data.

LogFileConfiguration.maxRotateCount renamed to maxKeptFiles​

The maxRotateCount property on LogFileConfiguration has been renamed to maxKeptFiles. Update any references accordingly:

// Before (v3)
final config = LogFileConfiguration(
directory: logDir,
maxRotateCount: 5,
);

// After (v4)
final config = LogFileConfiguration(
directory: logDir,
maxKeptFiles: 5,
);

Query boolean expressions return booleans instead of integers​

In v3, boolean-producing query expressions (comparisons, logical operators, null checks, like(), between(), in_(), is_(), Meta.isDeleted, quantifiers) returned 1 and 0 as integers.

In v4, these expressions return native true and false boolean values. If your code checks query results for == 1 or == 0 instead of using them as booleans, you will need to update those checks.

Revision IDs are no longer deterministic​

CBL 4 changed its revision ID generation algorithm. Revision IDs are no longer derived deterministically from document content. Code that hardcodes or predicts revision IDs based on document content will break.

Conflict resolver exception handling​

In v3, when a custom ConflictResolver threw an exception, the native layer would propagate a C++ exception across library boundaries. This was undefined behavior and caused crashes on some platforms (notably Windows).

In v4, when a ConflictResolver throws:

  1. The exception is reported on the Dart side as an unhandled error in the current zone (unchanged).
  2. On the native side, the CBLDefaultConflictResolver is called as a fallback instead of propagating a C++ exception. This resolver uses timestamp-based logic: the document with the newer timestamp wins, or if either side deleted the document, the deletion wins.

This means a throwing conflict resolver no longer causes cblite to report a conflict resolution error — the conflict is silently resolved using the default strategy. Applications that relied on detecting conflict resolver failures should catch exceptions within their ConflictResolver.resolve implementation and handle them explicitly.

DefaultConflictResolver uses timestamp-based logic​

In v3, the DefaultConflictResolver used revision ID string comparison to pick the winning document during replication conflicts. Since v4 revision IDs are no longer deterministic (see above), this strategy is no longer meaningful.

In v4, DefaultConflictResolver uses HLC timestamp comparison instead, matching the behavior of the native CBLDefaultConflictResolver: the document with the later timestamp wins, and if either side is a deletion, the deletion wins. This is the same strategy used as the fallback when a custom conflict resolver throws an exception.

DefaultConflictResolver is now also exported from the public API, so you can use it directly in a custom ConflictResolver to apply the default strategy for some documents:

class MyConflictResolver extends ConflictResolver {
const MyConflictResolver();


FutureOr<Document?> resolve(Conflict conflict) {
if (shouldUseCustomStrategy(conflict)) {
return customResolve(conflict);
}
// Fall back to the default timestamp-based strategy.
return const DefaultConflictResolver().resolve(conflict);
}
}

If your code relied on revision ID ordering to predict conflict outcomes, update it to account for the new timestamp-based behavior.

Conflict handling in saveDocument and deleteDocument​

In v3, Collection.saveDocument and Collection.deleteDocument returned false when a conflict occurred with ConcurrencyControl.failOnConflict.

In v4, these methods return void and throw a DatabaseException with DatabaseErrorCode.conflict instead. This aligns with other Couchbase Lite SDKs and makes conflicts harder to accidentally ignore.

The same applies to Collection.deleteTypedDocument and SaveTypedDocument.withConcurrencyControl.

// Before (v3)
final saved = await collection.saveDocument(
doc,
ConcurrencyControl.failOnConflict,
);
if (!saved) {
// handle conflict
}

// After (v4)
try {
await collection.saveDocument(
doc,
ConcurrencyControl.failOnConflict,
);
} on DatabaseException catch (e) {
if (e.code == DatabaseErrorCode.conflict) {
// handle conflict
} else {
rethrow;
}
}

Removed deprecated Database APIs​

In v3, Database provided document operations, change listeners, and index management directly. These were deprecated in favor of the collection-based API. In v4, these deprecated methods have been removed. Use the equivalent methods on Collection (typically via defaultCollection) instead.

Document operations​

// Before (v3)
final doc = await db.document('doc-id');
await db.saveDocument(mutableDoc);
await db.deleteDocument(doc);
await db.purgeDocumentById('doc-id');
await db.setDocumentExpiration('doc-id', expiry);
final count = await db.count;

// After (v4)
final collection = await db.defaultCollection;
final doc = await collection.document('doc-id');
await collection.saveDocument(mutableDoc);
await collection.deleteDocument(doc);
await collection.purgeDocumentById('doc-id');
await collection.setDocumentExpiration('doc-id', expiry);
final count = collection.count;

The following Database methods have been removed:

  • document, typedDocument, operator []
  • saveDocument, saveDocumentWithConflictHandler, saveTypedDocument
  • deleteDocument, deleteTypedDocument
  • purgeDocument, purgeTypedDocument, purgeDocumentById
  • setDocumentExpiration, getDocumentExpiration
  • count

Change listeners​

// Before (v3)
db.addChangeListener((change) { /* DatabaseChange */ });
db.addDocumentChangeListener('doc-id', (change) { ... });
db.changes(); // Stream<DatabaseChange>
db.documentChanges('doc-id');

// After (v4)
final collection = await db.defaultCollection;
collection.addChangeListener((change) { /* CollectionChange */ });
collection.addDocumentChangeListener('doc-id', (change) { ... });
collection.changes(); // Stream<CollectionChange>
collection.documentChanges('doc-id');

The DatabaseChange class and DatabaseChangeListener typedef have been removed. Use CollectionChange and CollectionChangeListener instead.

Index management​

// Before (v3)
await db.createIndex('myIndex', index);
await db.deleteIndex('myIndex');
final names = await db.indexes;

// After (v4)
final collection = await db.defaultCollection;
await collection.createIndex('myIndex', index);
await collection.deleteIndex('myIndex');
final names = await collection.indexes;

Removed deprecated Replicator APIs​

ReplicatorConfiguration changes​

The database parameter and all collection-level options on ReplicatorConfiguration have been removed. Use CollectionConfiguration and ReplicatorConfiguration.addCollection instead.

// Before (v3)
final config = ReplicatorConfiguration(
database: db,
target: urlEndpoint,
channels: ['channel1'],
pushFilter: myPushFilter,
conflictResolver: myResolver,
);

// After (v4)
final collection = await db.defaultCollection;
final config = ReplicatorConfiguration(target: urlEndpoint);
config.addCollection(collection, CollectionConfiguration(
channels: ['channel1'],
pushFilter: myPushFilter,
conflictResolver: myResolver,
));

The following ReplicatorConfiguration properties have been removed:

  • database
  • channels, documentIds
  • pushFilter, typedPushFilter, pullFilter, typedPullFilter
  • conflictResolver, typedConflictResolver

Pending document APIs​

// Before (v3)
final ids = await replicator.pendingDocumentIds;
final isPending = await replicator.isDocumentPending('doc-id');

// After (v4)
final ids = await replicator.pendingDocumentIdsInCollection(collection);
final isPending = await replicator.isDocumentPendingInCollection('doc-id', collection);

Removed DataSource.database​

Use DataSource.collection instead:

// Before (v3)
final query = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.database(db));

// After (v4)
final collection = await db.defaultCollection;
final query = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.collection(collection));

Removed InvalidJsonException​

The deprecated InvalidJsonException class has been removed. Use FleeceException instead.

Removed TracingDelegate API​

The TracingDelegate API and related tracing types (TracedOperation, ChannelCallOp, NativeCallOp, etc.) have been removed from the public API. This API will be replaced by an OpenTelemetry-based instrumentation API in a future release.

If you were using TracingDelegate directly, remove any references to it. The cbl_sentry package continues to provide Sentry integration for Couchbase Lite operations and does not require any changes.

Dart SDK requirement​

Version 4 requires Dart SDK ^3.10.0 due to the use of Dart native assets.

Native library management​

In v3, native libraries had to be downloaded or built manually (for standalone Dart via CouchbaseLiteDart.init, for Flutter via prebuilt platform packages).

In v4, the build hook at packages/cbl/hook/build.dart handles everything automatically:

  • Downloads libcblite for the target platform.
  • Compiles libcblitedart from source.
  • Optionally downloads the vector search extension.

No manual steps are required.

Step-by-step migration checklist​

  1. Update Dart SDK to ^3.10.0.
  2. Remove old packages from your pubspec.yaml:
    • cbl_dart
    • cbl_flutter
    • cbl_flutter_ce or cbl_flutter_ee
  3. Keep or add the cbl dependency.
  4. Configure edition (if using Enterprise) in pubspec.yaml under hooks.user_defines.cbl. In a Dart pub workspace, use the workspace root pubspec.yaml.
  5. Update imports:
    • Replace import 'package:cbl_flutter/cbl_flutter.dart' with import 'package:cbl/cbl.dart'.
    • Replace import 'package:cbl_dart/cbl_dart.dart' with import 'package:cbl/cbl.dart'.
  6. Remove initialization: Remove calls to CouchbaseLiteFlutter.init() or CouchbaseLiteDart.init(...). Initialization is no longer required. If you customized the default database directory via the filesDir parameter, use Database.defaultDirectory instead.
  7. Enable vector search (if needed) via pubspec.yaml config and an explicit call to Extension.enableVectorSearch.
  8. Rename langauge to language on FullTextIndex.
  9. Update certificate fields on ReplicatorConfiguration — wrap pinnedServerCertificate bytes with DerData or PemData, and trustedRootCertificates bytes with PemData.
  10. Update MutableDocument constructors — replace MutableDocument() with MutableDocument({}) and MutableDocument.withId(id, data) with MutableDocument(id: id, data).
  11. Rename maxRotateCount to maxKeptFiles in any LogFileConfiguration usage.
  12. Update query result checks that compare boolean expressions against 1 or 0 — they now return true/false.
  13. Remove hardcoded revision IDs — revision IDs are no longer deterministic.
  14. Review conflict resolution logic — DefaultConflictResolver now uses timestamp comparison instead of revision ID comparison.
  15. Update conflict handling — saveDocument and deleteDocument with failOnConflict now throw DatabaseException instead of returning false.
  16. Migrate Database document/index/listener APIs to use defaultCollection methods instead.
  17. Migrate ReplicatorConfiguration — replace database parameter with addCollection, move channels/documentIds/filters/resolvers into CollectionConfiguration.
  18. Migrate Replicator pending document APIs — replace pendingDocumentIds/isDocumentPending with pendingDocumentIdsInCollection/isDocumentPendingInCollection.
  19. Replace DataSource.database(db) with DataSource.collection(collection).
  20. Replace InvalidJsonException with FleeceException.
  21. Remove TracingDelegate usage — the tracing API has been removed from the public API.
  22. Run your app — native libraries will be built automatically on first run.