diff --git a/docs/api/database.md b/docs/api/database.md
index c019356..7145d71 100644
--- a/docs/api/database.md
+++ b/docs/api/database.md
@@ -36,6 +36,14 @@ firestack.database()
 ```
 Useful for `orderByPriority` queries.
 
+
+Transaction Support:
+```javascript
+firestack.database()
+  .ref('posts/1234/title')
+  .transaction((title) => 'My Awesome Post');
+```
+
 ## Unmounted components
 
 Listening to database updates on unmounted components will trigger a warning:
diff --git a/ios/Firestack/FirestackDatabase.h b/ios/Firestack/FirestackDatabase.h
index 77aa8f3..850a36a 100644
--- a/ios/Firestack/FirestackDatabase.h
+++ b/ios/Firestack/FirestackDatabase.h
@@ -18,6 +18,8 @@
 }
 
 @property NSMutableDictionary *dbReferences;
+@property NSMutableDictionary *transactions;
+@property dispatch_queue_t transactionQueue;
 
 @end
 
diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m
index 314500e..5cd833d 100644
--- a/ios/Firestack/FirestackDatabase.m
+++ b/ios/Firestack/FirestackDatabase.m
@@ -21,6 +21,8 @@ @interface FirestackDBReference : NSObject
 @property FIRDatabaseHandle childRemovedHandler;
 @property FIRDatabaseHandle childMovedHandler;
 @property FIRDatabaseHandle childValueHandler;
++ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot;
+
 @end
 
 @implementation FirestackDBReference
@@ -52,7 +54,7 @@ - (void) addEventHandler:(NSString *) eventName
 {
     if (![self isListeningTo:eventName]) {
         id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
-            NSDictionary *props = [self snapshotToDict:snapshot];
+            NSDictionary *props = [FirestackDBReference snapshotToDict:snapshot];
             [self sendJSEvent:DATABASE_DATA_EVENT
                         title:eventName
                         props: @{
@@ -142,7 +144,7 @@ - (void) removeEventHandler:(NSString *) name
     [self unsetListeningOn:name];
 }
 
-- (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
++ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
 {
     NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
     [dict setValue:snapshot.key forKey:@"key"];
@@ -377,6 +379,8 @@ - (id) init
     self = [super init];
     if (self != nil) {
         _dbReferences = [[NSMutableDictionary alloc] init];
+        _transactions = [[NSMutableDictionary alloc] init];
+        _transactionQueue = dispatch_queue_create("com.fullstackreact.react-native-firestack", DISPATCH_QUEUE_CONCURRENT);
     }
     return self;
 }
@@ -479,7 +483,85 @@ - (id) init
     }
 }
 
+RCT_EXPORT_METHOD(beginTransaction:(NSString *) path
+                  withIdentifier:(NSString *) identifier
+                  applyLocally:(BOOL) applyLocally
+                  onComplete:(RCTResponseSenderBlock) onComplete)
+{
+    dispatch_async(_transactionQueue, ^{
+        NSMutableDictionary *transactionState = [NSMutableDictionary new];
+        
+        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+        [transactionState setObject:sema forKey:@"semaphore"];
+        
+        FIRDatabaseReference *ref = [self getPathRef:path];
+        [ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
+            dispatch_barrier_async(_transactionQueue, ^{
+                [_transactions setValue:transactionState forKey:identifier];
+                [self sendEventWithName:DATABASE_TRANSACTION_EVENT
+                                   body:@{
+                                          @"id": identifier,
+                                          @"originalValue": currentData.value
+                                          }];
+            });
+            // Wait for the event handler to call tryCommitTransaction
+            // WARNING: This wait occurs on the Firebase Worker Queue
+            // so if tryCommitTransaction fails to signal the semaphore
+            // no further blocks will be executed by Firebase until the timeout expires
+            dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
+            BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
+            BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
+            id value = [transactionState valueForKey:@"value"];
+            dispatch_barrier_async(_transactionQueue, ^{
+                [_transactions removeObjectForKey:identifier];
+            });
+            if (abort) {
+                return [FIRTransactionResult abort];
+            } else {
+                currentData.value = value;
+                return [FIRTransactionResult successWithValue:currentData];
+            }
+        } andCompletionBlock:^(NSError * _Nullable databaseError, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
+            if (databaseError != nil) {
+                NSDictionary *evt = @{
+                                      @"errorCode": [NSNumber numberWithInt:[databaseError code]],
+                                      @"errorDetails": [databaseError debugDescription],
+                                      @"description": [databaseError description]
+                                      };
+                onComplete(@[evt]);
+            } else {
+                onComplete(@[[NSNull null], @{
+                               @"committed": [NSNumber numberWithBool:committed],
+                               @"snapshot": [FirestackDBReference snapshotToDict:snapshot],
+                               @"status": @"success",
+                               @"method": @"transaction"
+                               }]);
+            }
+        } withLocalEvents:applyLocally];
+    });
+}
 
+RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier
+                  withData:(NSDictionary *) data
+                  orAbort:(BOOL) abort)
+{
+    __block NSMutableDictionary *transactionState;
+    dispatch_sync(_transactionQueue, ^{
+        transactionState = [_transactions objectForKey: identifier];
+    });
+    if (!transactionState) {
+        NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
+        return;
+    }
+    dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
+    if (abort) {
+        [transactionState setValue:@true forKey:@"abort"];
+    } else {
+        id newValue = [data valueForKey:@"value"];
+        [transactionState setValue:newValue forKey:@"value"];
+    }
+    dispatch_semaphore_signal(sema);
+}
 
 RCT_EXPORT_METHOD(on:(NSString *) path
                   modifiersString:(NSString *) modifiersString
@@ -634,7 +716,7 @@ - (NSString *) getDBListenerKey:(NSString *) path
 
 // Not sure how to get away from this... yet
 - (NSArray<NSString *> *)supportedEvents {
-    return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
+    return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
 }
 
 
diff --git a/ios/Firestack/FirestackEvents.h b/ios/Firestack/FirestackEvents.h
index fd6b01d..68ead8b 100644
--- a/ios/Firestack/FirestackEvents.h
+++ b/ios/Firestack/FirestackEvents.h
@@ -29,6 +29,7 @@ static NSString *const DEBUG_EVENT = @"debug";
 // Database
 static NSString *const DATABASE_DATA_EVENT = @"database_event";
 static NSString *const DATABASE_ERROR_EVENT = @"database_error";
+static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_update";
 
 static NSString *const DATABASE_VALUE_EVENT = @"value";
 static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added";
diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js
index e117648..f3eecdd 100644
--- a/lib/modules/database/index.js
+++ b/lib/modules/database/index.js
@@ -19,7 +19,10 @@ export default class Database extends Base {
   constructor(firestack: Object, options: Object = {}) {
     super(firestack, options);
     this.subscriptions = {};
+
+    this.transactions = {};
     this.errorSubscriptions = {};
+
     this.serverTimeOffset = 0;
     this.persistenceEnabled = false;
     this.namespace = 'firestack:database';
@@ -34,6 +37,11 @@ export default class Database extends Base {
       err => this._handleDatabaseError(err)
     );
 
+    this.transactionListener = FirestackDatabaseEvt.addListener(
+      'database_transaction_update',
+      event => this._handleDatabaseTransaction(event)
+    );
+
     this.offsetRef = this.ref('.info/serverTimeOffset');
 
     this.offsetRef.on('value', (snapshot) => {
@@ -164,6 +172,34 @@ export default class Database extends Base {
     FirestackDatabase.goOffline();
   }
 
+  addTransaction(path, updateCallback, applyLocally) {
+    let id = this._generateTransactionID();
+    this.transactions[id] = updateCallback;
+    return promisify('beginTransaction', FirestackDatabase)(path, id, applyLocally || false)
+      .then((v) => {delete this.transactions[id]; return v;},
+            (e) => {delete this.transactions[id]; throw e;}); 
+  }
+
+  _generateTransactionID() {
+    // 10 char random alphanumeric
+    return Math.random().toString(36).substr(2, 10);
+  }
+
+  _handleDatabaseTransaction(event) {
+    const {id, originalValue} = event;
+    let newValue;
+    try {
+      const updateCallback = this.transactions[id];
+      newValue = updateCallback(originalValue);
+    } finally {
+      let abort = false;
+      if (newValue === undefined) {
+        abort = true;
+      }
+      FirestackDatabase.tryCommitTransaction(id, {value: newValue}, abort);
+    }
+  }
+
   /**
    *  INTERNALS
    */
diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js
index e74b5aa..adc6952 100644
--- a/lib/modules/database/reference.js
+++ b/lib/modules/database/reference.js
@@ -137,6 +137,19 @@ export default class Reference extends ReferenceBase {
     return this.db.off(path, modifiersString, eventName, origCB);
   }
 
+  transaction(transactionUpdate, onComplete, applyLocally) {
+    const path = this._dbPath();
+    return this.db.addTransaction(path, transactionUpdate, applyLocally)
+      .then((({ snapshot, committed }) => {return {snapshot: new Snapshot(this, snapshot), committed}}).bind(this))
+      .then(({ snapshot, committed }) => {
+        if (isFunction(onComplete)) onComplete(null, snapshot);
+        return {snapshot, committed};
+      }).catch((e) => {
+        if (isFunction(onComplete)) return onComplete(e, null);
+        throw e;
+      });
+  }
+
   /**
    * MODIFIERS
    */