Write Data to a Synced Database
On this page
- Determining What Data Syncs
- App Services Configuration
- Client Data Model and Configuration
- What Data Syncs?
- Write to a Synced Database
- Successful Writes
- Compensating Writes
- Compensating Write Error Information
- Writes That Don't Match the Query Subscription
- Writes That Don't Match Permissions
- Group Writes for Improved Performance
- Multi-Process Sync Not Supported
- Crashes Related to Opening a Synced Database in Multiple Processes
- Alternatives to Writing to a Synced Database in Multiple Processes
- Pass Data On Disk
- Communicate Directly with the Backing Atlas Collection
When writing data to a synced database using Device Sync, you can use the same APIs as writing to a non-synced database. However, there are some differences in behavior to keep in mind as you develop your application.
When you write to a synced database, your write operations must match both of the following:
- The sync subscription query.
If your write operation doesn't match the query in the subscription, the write reverts with a non-fatal compensating write error.
- The permissions in your App Services App.
If your try to write data that doesn't match the permissions expression, the write reverts with a non-fatal permission denied error. In the client, this shows as a compensating write error. On the server, you can see more details about how the write was denied was by a write filter in the role.
To learn more about configuring permissions for your app, see Role-based Permissions and the Device Sync Permissions Guide in the App Services documentation.
Warning
Multi-Process Sync is Not Supported
Device Sync does not currently support opening or writing to a synced database from more than one process. For more information, including suggested alternatives, refer to: Multi-Process Sync Not Supported.
Determining What Data Syncs
The data that you can write to a synced database is the intersection of:
Your Device Sync configuration.
Your Atlas server-side permissions.
The Sync subscription query that you use when you open the database.
The examples on this page use the following configurations and models:
App Services Configuration
Device Sync is configured with the following queryable fields:
_id
(always included)complexity
ownerId
The App Services App has permissions configured to let users read and write only their own data:
{ "name": "owner-read-write", "apply_when": {}, "document_filters": { "read": { "ownerId": "%%user.id" }, "write": { "ownerId": "%%user.id" } }, "read": true, "write": true }
Client Data Model and Configuration
The examples on this page use the following object model:
struct Item { realm::primary_key<realm::object_id> _id{realm::object_id::generate()}; std::string ownerId; std::string itemName; int64_t complexity; }; REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)
// The documentation does not currently have this code example in C#. // Please refer to the other languages or related pages for example code.
()class _Car { "_id") ( () late ObjectId id; // This is the queryable field late String ownerId; late String make; late String? model; late int? miles; }
// The documentation does not currently have this code example in JavaScript. // Please refer to the other languages or related pages for example code.
class Item : RealmObject { var _id: ObjectId = ObjectId() var ownerId: String = "" var itemName: String = "" var complexity: Int = 0 }
class Item: Object { true) var _id: ObjectId (primaryKey: var ownerId: String var itemName: String var complexity: Int }
// The documentation does not currently have this code example in TypeScript. // Please refer to the other languages or related pages for example code.
Using that object model, the synced database configuration syncs objects that
match the subscription query where the complexity
property's value
is less than or equal to 4
:
auto appConfig = realm::App::configuration(); appConfig.app_id = APP_ID; auto app = realm::App(appConfig); auto user = app.login(realm::App::credentials::anonymous()).get(); auto dbConfig = user.flexible_sync_configuration(); auto syncRealm = realm::db(dbConfig); // Add subscription auto subscriptionUpdateSuccess = syncRealm.subscriptions() .update([](realm::mutable_sync_subscription_set &subs) { // Get Items from Atlas that match this query. // Uses the queryable field `complexity`. // Sync Item objects with complexity less than or equal to 4. subs.add<realm::Item>( "simple items", [](auto &obj) { return obj.complexity <= 4; }); }) .get();
realm.Subscriptions.Update(() => { var completedItemsQuery = realm .All<MyTask>() .Where(i => i.Status == "completed"); realm.Subscriptions .Add(completedItemsQuery, new SubscriptionOptions() { Name = "completedItems" }); });
final app = App(AppConfiguration(APP_ID)); final user = await app.logIn(Credentials.anonymous()); final config = Configuration.flexibleSync(user, [Car.schema]); final realm = Realm(config); // Add subscriptions realm.subscriptions.update((mutableSubscriptions) { // Get Cars from Atlas that match the Realm Query Language query. // Uses the queryable field `miles`. // Query matches cars with less than 100 miles or `null` miles. final newCarQuery = realm.query<Car>("miles < 100 OR miles == \$0", [null]); mutableSubscriptions.add(newCarQuery, name: "new-car-subscription"); }); await realm.subscriptions.waitForSynchronization();
// The documentation does not currently have this code example in JavaScript. // Please refer to the other languages or related pages for example code.
val app = App.create(FLEXIBLE_APP_ID) val user = app.login(credentials) val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Item::class)) // Add subscription .initialSubscriptions { realm -> add( // Get Items from Atlas that match the Realm Query Language query. // Uses the queryable field `complexity`. // Query matches objects with complexity less than or equal to 4. realm.query<Item>("complexity <= 4"), "simple-items" ) } .build() val syncRealm = Realm.open(flexSyncConfig) syncRealm.subscriptions.waitForSynchronization() Log.v("Successfully opened realm: ${syncRealm.configuration}")
let app = App(id: YOUR_APP_ID_HERE) do { let user = try await app.login(credentials: Credentials.anonymous) do { var flexSyncConfig = user.flexibleSyncConfiguration() flexSyncConfig.objectTypes = [Item.self] let realm = try await Realm(configuration: flexSyncConfig) let subscriptions = realm.subscriptions try await subscriptions.update { subscriptions.append( QuerySubscription<Item>(name: "simple-items") { $0.complexity <= 4 }) } print("Successfully opened realm: \(realm)") } catch { print("Failed to open realm: \(error.localizedDescription)") // handle error } } catch { fatalError("Login failed: \(error.localizedDescription)") }
// The documentation does not currently have this code example in TypeScript. // Please refer to the other languages or related pages for example code.
What Data Syncs?
The subscription query combined with the permissions mean that the synced database only syncs objects where:
The
ownerId
matches the user ID of the logged-in user (from the permissions)The
complexity
property's value is less than or equal to4
(from the subscription query)
Any object in the Atlas collection where the ownerId
does not match
the user ID of the logged-in user, or the complexity
property's
value is greater than 4
, cannot sync to this database.
Write to a Synced Database
Writes to synced databases may broadly fall into one of two categories:
Successful writes: The written object matches both the query subscription and the user's permissions. The object writes successfully to the database, and syncs successfully to the App Services backend and other devices.
Compensating writes: When the written object does not match the subscription query, or where the user does not have sufficient permissions to perform the write, the SDK reverts the illegal write.
Successful Writes
When the write matches both the permissions and the Sync subscription query in the client, the SDK can successfully write the object to the synced database. This object syncs with the App Services backend when the device has a network connection.
// Per the Device Sync permissions, users can only read and write data // where the `Item.ownerId` property matches their own user ID. auto simpleItem = realm::Item{.ownerId = user.identifier(), .itemName = "This item meets sync criteria", .complexity = 3}; // `simpleItem` successfully writes to the realm and syncs to Atlas // because its data matches the subscription query (complexity <= 4) // and its `ownerId` field matches the user ID. syncRealm.write([&] { syncRealm.add(std::move(simpleItem)); });
// The documentation does not currently have this code example in C#. // Please refer to the other languages or related pages for example code.
// Per the Device Sync permissions, users can only read and write data // where the `Car.ownerId` property matches their own user ID. final userId = user.id; realm.write(() { // WRITE SUCCEEDS // `newCar` is successfully written to the realm and synced to Atlas // because it's data matches the subscription query (miles < 100) // and it's `ownerId` field matches the user ID. final newCar = Car(ObjectId(), userId, 'Toyota', miles: 2); realm.add(newCar); });
// The documentation does not currently have this code example in JavaScript. // Please refer to the other languages or related pages for example code.
// Per the Device Sync permissions, users can only read and write data // where the `Item.ownerId` property matches their own user ID. val userId = user.id val newItem = Item().apply { ownerId = userId itemName = "This item meets sync criteria" complexity = 3 } syncRealm.write { // `newItem` is successfully written to the realm and synced to Atlas // because its data matches the subscription query (complexity <= 4) // and its `ownerId` field matches the user ID. copyToRealm(newItem) }
// This write falls within the subscription query and complies // with the Device Sync permissions, so this write should succeed. do { let learnRealm = Item() learnRealm.ownerId = user.id learnRealm.itemName = "Learn Realm CRUD stuff" learnRealm.complexity = 3 try realm.write { realm.add(learnRealm) } } catch { print("Failed to write to realm: \(error.localizedDescription)") }
// The documentation does not currently have this code example in TypeScript. // Please refer to the other languages or related pages for example code.
Compensating Writes
In some cases, a write that initially appears to succeed is actually an illegal write. In these cases, the object writes to the database, but when the database syncs to the backend, the SDK reverts the write in a non-fatal error operation called a compensating write. Compensating writes can occur when:
Writes don't match the query subscription: The written object matches the user's permissions, but does not match the query subscription.
Writes don't match permissions: The written object matches the query subscription, but does not match the user's permissions.
In more detail, when you write data that is outside the bounds of a query subscription or does not match the user's permissions, the following occurs:
Because the client database has no concept of "illegal" writes, the write initially succeeds until the SDK resolves the changeset with the App Services backend.
Upon sync, the server applies the rules and permissions. The server determines that the user does not have authorization to perform the write.
The server sends a revert operation, called a "compensating write", back to the client.
The client's database reverts the illegal write operation.
Any client-side writes to a given object between an illegal write to that object and the corresponding compensating write will be lost.
In practice, this may look like an object being written to the database, and then disappearing after the server sends the compensating write back to the client.
To learn more about permission denied errors, compensating write errors and other Device Sync error types, refer to Sync Errors in the App Services documentation.
The App Services logs contain more information about why a compensating write error occurs.
Compensating Write Error Information
You can get additional information in the client about why a compensating write occurs.
The compensating_writes_info()
function provides an array of compensating_write_error_info
structs that contain:
The
object_name
of the object the client attempted to writeThe
primary_key
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. The C++ SDK exposes this object on the client for convenience and debugging purposes.
The following shows an example of how you might log information about compensating write errors:
auto info = receivedSyncError.compensating_writes_info(); for (auto &v : info) { std::cout << "A write was rejected with a compensating write error.\n"; std::cout << "An object of type " << v.object_name << "\n"; std::cout << "was rejected because " << v.reason << ".\n"; }
A write was rejected with a compensating write error. An object of type Item was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in table "Item" not allowed; object is outside of the current query view.
The
Item
in this message isItem
object used in the object model.The
table "Item"
refers to the Atlas collection where this object would sync.The reason
object is outside of the current query view
in this message is because the query subscription was set to require the object'scomplexity
property to be less than or equal to4
. The client attempted to write an object outside of this boundary.The primary key is the
objectId
of the specific object the client attempted to write.
The .NET SDK exposes a CompensatingWrites
field on a
CompensatingWriteException.
You can access this information through the Sync error handler.
When a CompensatingWriteException
is thrown, it includes an enumerable of
CompensatingWriteInfo
objects. Each CompensatingWriteInfo
object contains properties that describe
the object type, its primary key, and reason the server performed the compensating
write.
This information is the same information you can find in the App Services logs. It is exposed on the client for convenience and debugging purposes.
The Flutter SDK exposes a compensatingWrites
field on a
CompensatingWriteError.
You can access this information through the Sync error handler.
This field contains an array of CompensatingWriteInfo objects, which provide:
The
objectType
of the object the client attempted to writeThe
primaryKey
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. It is exposed on the client for convenience and debugging purposes.
The Node.js SDK exposes a writes
field on a
CompensatingWriteError.
You can access this information through the Sync error handler.
This field contains an array of CompensatingWriteInfo objects, which provide:
The
objectName
of the object the client attempted to writeThe
primaryKey
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. It is exposed on the client for convenience and debugging purposes.
The CompensatingWriteInfo object provides:
The
objectType
of the object the client attempted to writeThe
primaryKey
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. The Kotlin SDK exposes this object on the client for convenience and debugging purposes.
The following shows an example of how you might log information about compensating write errors:
val syncErrorHandler = SyncSession.ErrorHandler { session, error -> runBlocking { if (error is CompensatingWriteException) { error.writes.forEach { writeInfo -> val errorMessage = """ A write was rejected with a compensating write error The write to object type: ${writeInfo.objectType} With primary key of: ${writeInfo.primaryKey} Was rejected because: ${writeInfo.reason} """.trimIndent() Log.e(errorMessage) } } } }
A write was rejected with a compensating write error The write to object type: Item With primary key of: RealmAny{type=OBJECT_ID, value=BsonObjectId(649f2c38835cc0346b861b74)} Was rejected because: write to "649f2c38835cc0346b861b74" in table "Item" not allowed; object is outside of the current query view
The
Item
in this message isItem
object used in the object model on this page.The primary key is the
objectId
of the specific object the client attempted to write.The
table "Item"
refers to the Atlas collection where this object would sync.The reason
object is outside of the current query view
in this example is because the query subscription was set to require the object'scomplexity
property to be less than or equal to4
, and the client attempted to write an object outside of this boundary.
The Swift SDK exposes a compensatingWriteInfo field on a
SyncError whose code is .writeRejected
. You can access this information
through the Sync error handler.
This field contains an array of RLMCompensatingWriteInfo objects, which provide:
The
objectType
of the object the client attempted to writeThe
primaryKey
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. It is exposed on the client for convenience and debugging purposes.
Example
This error handler shows an example of how you might log information about compensating write errors:
myApp.syncManager.errorHandler = { syncError, session in if let thisError = syncError as? SyncError { switch thisError.code { // ... additional SyncError.code cases ... case .writeRejected: if let compensatingWriteErrorInfo = thisError.compensatingWriteInfo { for anError in compensatingWriteErrorInfo { print("A write was rejected with a compensating write error") print("The write to object type: \(anError.objectType)") print("With primary key of: \(anError.primaryKey)") print("Was rejected because: \(anError.reason)") } } } } }
The example error handler above produces this output when a compensating write error occurs:
A write was rejected with a compensating write error The write to object type: Optional("Item") With primary key of: objectId(641382026852d9220b2e2bbf) Was rejected because: Optional("write to \"641382026852d9220b2e2bbf\" in table \"Item\" not allowed; object is outside of the current query view")
The
Optional("Item")
in this message is anItem
object used in the Swift Template App.The primary key is the
objectId
of the specific object the client attempted to write.The
table \"Item"\
refers to the Atlas collection where this object would sync.The reason
object is outside of the current query view
in this example is because the query subscription was set to require the object'sisComplete
property to betrue
, and the client attempted to write an object where this property wasfalse
.
The Node.js SDK exposes a writes
field on a
CompensatingWriteError.
You can access this information through the Sync error handler.
This field contains an array of CompensatingWriteInfo objects, which provide:
The
objectName
of the object the client attempted to writeThe
primaryKey
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. It is exposed on the client for convenience and debugging purposes.
Writes That Don't Match the Query Subscription
You can only write objects to a synced database if they match the subscription query. If you perform a write that does not match the subscription query, the SDK initially writes the object, but then performs a compensating write. This is a non-fatal operation that reverts an illegal write that does not match the subscription query.
In practice, this may look like the write succeeding, but then the object "disappears" when the SDK syncs with the App Services backend and performs the compensating write.
If you want to write an object that does not match the query subscription, you must open a different database where the object matches the query subscription. Alternately, you could write the object to a non-synced database that does not enforce permissions or subscription queries.
Code Example
Given the configuration for the synced database above, attempting to write this object does not match the query subscription:
// The complexity of this item is `7`. This is outside the bounds // of the subscription query, which triggers a compensating write. auto complexItem = realm::Item{._id = primaryKey, .ownerId = user.identifier(), .itemName = "Test compensating writes", .complexity = 7}; // This should trigger a compensating write error when it tries to sync // due to server-side permissions, which gets logged with the error handler. syncRealm.write([&] { syncRealm.add(std::move(complexItem)); });
Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)
// The documentation does not currently have this code example in C#. // Please refer to the other languages or related pages for example code.
final carId = ObjectId(); final ownerId = app.currentUser!.id; realm.write(() { // WRITE REVERTED BY QUERY SUBSCRIPTION COMPENSATING WRITE // `oldCar` is initially written to the realm, then later removed // in a compensating write when the server processes the write. // This is because the `miles` property of `oldCar` doesn't match // the subscription query, which is only for cars with less than 100 miles. final oldCar = Car(carId, ownerId, 'Honda', miles: 90000); realm.add(oldCar); }); // Let changes sync to and from server await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 21 in server version 2877: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
// The documentation does not currently have this code example in JavaScript. // Please refer to the other languages or related pages for example code.
// The complexity of this item is `7`. This is outside the bounds // of the subscription query, which triggers a compensating write. val itemTooComplex = Item().apply { ownerId = user.id itemName = "This item is too complex" complexity = 7 } syncRealm.write { copyToRealm(itemTooComplex) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
do { let fixTheBug = Item() fixTheBug.ownerId = user.id fixTheBug.itemName = "Fix the bug with the failing method" // The complexity of this item is `7`. This is outside the bounds // of the subscription query, so this write triggers a compensating write. fixTheBug.complexity = 7 try realm.write { realm.add(fixTheBug) } } catch { print("Failed to write to realm: \(error.localizedDescription)") }
Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning)
// The documentation does not currently have this code example in TypeScript. // Please refer to the other languages or related pages for example code.
App Services Error
The error message in the App Services logs in this scenario is:
"Item": { "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" in table \"Item\" not allowed; object is outside of the current query view" }
Writes That Don't Match Permissions
Attempting to write to the client can also trigger a compensating write error when the object does not match the user's server-side write permissions.
On the client, this type of write behaves the same as a write that doesn't match the query subscription. In practice, this may look like the write succeeding, but then the object "disappears" when the database syncs with the App Services backend and performs the compensating write.
Code Example
Given the permissions in the Device Sync Configuration detailed above,
attempting to write an object where the ownerId
property does not match
the user.id
of the logged-in user is not a legal write:
// The `ownerId` of this item does not match the user ID of the logged-in // user. The user does not have permissions to make this write, which // triggers a compensating write. auto itemWithWrongOwner = realm::Item{ .ownerId = "not the current user", .itemName = "Trigger an incorrect permissions compensating write", .complexity = 1}; syncRealm.write([&] { syncRealm.add(std::move(itemWithWrongOwner)); });
Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)
// The documentation does not currently have this code example in C#. // Please refer to the other languages or related pages for example code.
final carId = 'someOtherId'; realm.write(() { // WRITE REVERTED BY PERMISSION ERROR // `otherUsersCar` is initially written to the realm, then removed upon synchronization // because it's `ownerId` property doesn't match the user ID of the user // making the request. final otherUsersCar = Car(ObjectId(), carId, 'Ford'); realm.add(otherUsersCar); }); // sync changes await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 25 in server version 2879: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
// The documentation does not currently have this code example in JavaScript. // Please refer to the other languages or related pages for example code.
// The `ownerId` of this item does not match the `user.id` of the logged-in // user. The user does not have permissions to make this write, which // triggers a compensating write. val itemWithWrongOwner = Item().apply { ownerId = "not the current user" itemName = "A simple item" complexity = 1 } syncRealm.write { copyToRealm(itemWithWrongOwner) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
do { let itemWithWrongOwner = Item() // The `ownerId` of this item does not match the `user.id` of the logged-in // user. The user does not have permissions to make this write, so // it triggers a compensating write. itemWithWrongOwner.ownerId = "This string does not match the user.id" itemWithWrongOwner.itemName = "Write code that generates a permission error" itemWithWrongOwner.complexity = 1 try realm.write { realm.add(itemWithWrongOwner) } } catch { print("Failed to write to realm: \(error.localizedDescription)") }
Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning)
// The documentation does not currently have this code example in TypeScript. // Please refer to the other languages or related pages for example code.
App Services Error
The error message in the App Services logs provides some additional information to help you determine that it is a permissions issue, and not a query subscription issue. In this example, the error message shows that the the object does not match the user's role:
"Item": { "63bdfc40f16be7b1e8c7e4b8": "write to \"63bdfc40f16be7b1e8c7e4b8\" in table \"Item\" was denied by write filter in role \"owner-read-write\"" }
Group Writes for Improved Performance
Every write transaction for a subscription set has a performance cost. If you need to make multiple updates to a database object during a session, consider keeping edited objects in memory until all changes are complete. This improves sync performance by only writing the complete and updated object to your database instead of every change.
Multi-Process Sync Not Supported
If you are developing an app that may attempt to use a synced database in more than one process, such as an Apple device application using a Share Extension, avoid writing to a synced database in the additional process. Device Sync supports opening a synced database in at most one process. In practice, this means that if your app uses a synced database in an additional process, it may crash intermittently.
Crashes Related to Opening a Synced Database in Multiple Processes
If you attempt to open a synced database in a Share Extension or other
multiple-process use case, and that database is not already open in the main
app, a write from a Share Extension may succeed. However, if the synced
database is already open in the main app, or is syncing data in the background,
you may see a crash related to Realm::MultiSyncAgents
. In this scenario,
you may need to restart the device.
Alternatives to Writing to a Synced Database in Multiple Processes
If you need to read from or write to a synced database in more than one process, there are a few recommended alternatives:
Offline-first: pass data on disk to or from the main process or main app
Always up-to-date: communicate directly with the backing Atlas collection across a network connection
Pass Data On Disk
If offline-first functionality is the most important consideration for your app, you can pass data on disk to or from your main app. In an Apple ecosystem, for example, you could copy objects to a non-synced database and read and share it between apps in an App Group. Or you could use an on-disk queue to send the data to or from the main app and only write to the synced database from there. Then, regardless of the device's network connectivity, information can be shared any time to or from the App Extension.
Communicate Directly with the Backing Atlas Collection
If having the information always up-to-date across all devices is the most important consideration for your app, you can read or write data directly to or from the backing Atlas collection across the network. Depending on your needs, you may want to use one of these tools to communicate directly with Atlas:
Query Atlas with the the SDK MongoClient
Pass data to an App Services Function
Make HTTPS calls with the Data API
Then, any device that has a network connection is always getting the most up-to-date information, without waiting for the user to open your main app as in the option above.
This option does require your user's device to have a network connection when using the secondary process. As a fallback, you could check for a network connection. Then, use the on-disk option above in the event that the user's device lacks network connectivity.