diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 5ba4276..3d3434d 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -2,8 +2,6 @@ name: Deploy Docs
 
 on:
   push:
-    branches:
-      - main
 
 permissions:
   contents: read
@@ -47,6 +45,7 @@ jobs:
 
   # Deployment job
   deploy:
+    if: ${{ github.ref == 'refs/heads/main' }}
     environment:
       name: github-pages
       url: ${{ needs.build.outputs.page_url }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79d31c7..084ea12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## unreleased
+
+- Add sync progress information through `SyncStatusData.downloadProgress`.
+
 # 1.0.0
 
 - Improved the stability of watched queries. Watched queries were previously susceptible to runtime crashes if an exception was thrown in the update stream. Errors are now gracefully handled.
diff --git a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 4f14951..fea5149 100644
--- a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
 {
-  "originHash" : "33297127250b66812faa920958a24bae46bf9e9d1c38ea6b84ca413efaf16afd",
+  "originHash" : "2d885a1b46f17f9239b7876e3889168a6de98024718f2d7af03aede290c8a86a",
   "pins" : [
     {
       "identity" : "anycodable",
@@ -10,6 +10,15 @@
         "version" : "0.6.7"
       }
     },
+    {
+      "identity" : "powersync-kotlin",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/powersync-ja/powersync-kotlin.git",
+      "state" : {
+        "revision" : "ccd2e595195c59d570eb93a878ad6a5cfca72ada",
+        "version" : "1.0.1+SWIFT.0"
+      }
+    },
     {
       "identity" : "powersync-sqlite-core-swift",
       "kind" : "remoteSourceControl",
diff --git a/Demo/PowerSyncExample/Components/ListView.swift b/Demo/PowerSyncExample/Components/ListView.swift
index 21c1028..6e3ebc1 100644
--- a/Demo/PowerSyncExample/Components/ListView.swift
+++ b/Demo/PowerSyncExample/Components/ListView.swift
@@ -1,6 +1,7 @@
 import SwiftUI
 import IdentifiedCollections
 import SwiftUINavigation
+import PowerSync
 
 struct ListView: View {
     @Environment(SystemManager.self) private var system
@@ -9,18 +10,32 @@ struct ListView: View {
     @State private var error: Error?
     @State private var newList: NewListContent?
     @State private var editing: Bool = false
-    @State private var didSync: Bool = false
+    @State private var status: SyncStatusData? = nil
 
     var body: some View {
-        if !didSync {
-            Text("Busy with sync!").task {
-                do {
-                    try await system.db.waitForFirstSync(priority: 1)
-                    didSync = true;
-                } catch {}
+        if status?.hasSynced != true {
+            VStack {
+                if let status = self.status {
+                    if status.hasSynced != true {
+                        Text("Busy with initial sync...")
+                        
+                        if let progress = status.downloadProgress {
+                            ProgressView(value: progress.fraction)
+                            
+                            if progress.downloadedOperations == progress.totalOperations {
+                                Text("Applying server-side changes...")
+                            } else {
+                                Text("Downloaded \(progress.downloadedOperations) out of \(progress.totalOperations)")
+                            }
+                        }
+                    }
+                } else {
+                    ProgressView()
+                        .progressViewStyle(CircularProgressViewStyle())
+                }
             }
         }
-        
+
         List {
             if let error {
                 ErrorText(error)
@@ -79,6 +94,13 @@ struct ListView: View {
                 }
             }
         }
+        .task {
+            self.status = system.db.currentStatus
+            
+            for await status in system.db.currentStatus.asFlow() {
+                self.status = status
+            }
+        }
     }
 
     func handleDelete(at offset: IndexSet) async {
diff --git a/Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift b/Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift
index a1dd744..0d2d759 100644
--- a/Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift
+++ b/Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift
@@ -37,6 +37,11 @@ extension KotlinSyncStatusDataProtocol {
         )
     }
     
+    var downloadProgress: (any SyncDownloadProgress)? {
+        guard let kotlinProgress = base.downloadProgress else { return nil }
+        return KotlinSyncDownloadProgress(progress: kotlinProgress)
+    }
+    
     var hasSynced: Bool? {
         base.hasSynced?.boolValue
     }
@@ -80,3 +85,33 @@ extension KotlinSyncStatusDataProtocol {
         )
     }
 }
+
+protocol KotlinProgressWithOperationsProtocol: ProgressWithOperations {
+    var base: any PowerSyncKotlin.ProgressWithOperations { get }
+}
+
+extension KotlinProgressWithOperationsProtocol {
+    var totalOperations: Int32 {
+        return base.totalOperations
+    }
+    
+    var downloadedOperations: Int32 {
+        return base.downloadedOperations
+    }
+}
+
+struct KotlinProgressWithOperations: KotlinProgressWithOperationsProtocol {
+    let base: PowerSyncKotlin.ProgressWithOperations
+}
+
+struct KotlinSyncDownloadProgress: KotlinProgressWithOperationsProtocol, SyncDownloadProgress {
+    let progress: PowerSyncKotlin.SyncDownloadProgress
+    
+    var base: any PowerSyncKotlin.ProgressWithOperations {
+        progress
+    }
+    
+    func untilPriority(priority: BucketPriority) -> any ProgressWithOperations {
+        return KotlinProgressWithOperations(base: progress.untilPriority(priority: priority.priorityCode))
+    }
+}
diff --git a/Sources/PowerSync/Protocol/sync/DownloadProgress.swift b/Sources/PowerSync/Protocol/sync/DownloadProgress.swift
new file mode 100644
index 0000000..5b114ab
--- /dev/null
+++ b/Sources/PowerSync/Protocol/sync/DownloadProgress.swift
@@ -0,0 +1,60 @@
+/// Information about a progressing download.
+/// 
+/// This reports the ``totalOperations`` amount of operations to download, how many of them
+/// have already been downloaded as ``downloadedOperations`` and finally a ``fraction`` indicating
+/// relative progress.
+/// 
+/// To obtain a ``ProgressWithOperations`` instance, either use ``SyncStatusData/downloadProgress``
+/// for global progress or ``SyncDownloadProgress/untilPriority(priority:)``.
+public protocol ProgressWithOperations {
+    /// How many operations need to be downloaded in total for the current download
+    /// to complete.
+    var totalOperations: Int32 { get }
+
+    /// How many operations, out of ``totalOperations``, have already been downloaded.
+    var downloadedOperations: Int32 { get }
+}
+
+public extension ProgressWithOperations {
+    /// The relative amount of ``totalOperations`` to items in ``downloadedOperations``, as a
+    /// number between `0.0` and `1.0` (inclusive).
+    /// 
+    /// When this number reaches `1.0`, all changes have been received from the sync service.
+    /// Actually applying these changes happens before the ``SyncStatusData/downloadProgress``
+    /// field is cleared though, so progress can stay at `1.0` for a short while before completing.
+    var fraction: Float {
+        if (self.totalOperations == 0) {
+            return 0.0
+        }
+
+        return Float.init(self.downloadedOperations) / Float.init(self.totalOperations)
+    }
+}
+
+/// Provides realtime progress on how PowerSync is downloading rows.
+/// 
+/// This type reports progress by extending ``ProgressWithOperations``, meaning that the
+/// ``ProgressWithOperations/totalOperations``, ``ProgressWithOperations/downloadedOperations``
+/// and ``ProgressWithOperations/fraction`` properties are available on this instance.
+/// Additionally, it's possible to obtain progress towards a specific priority only (instead
+/// of tracking progress for the entire download) by using ``untilPriority(priority:)``.
+/// 
+/// The reported progress always reflects the status towards the end of a sync iteration (after
+/// which a consistent snapshot of all buckets is available locally).
+/// 
+/// In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
+/// operation takes place between syncs), it's possible for the returned numbers to be slightly
+/// inaccurate. For this reason, ``SyncDownloadProgress`` should be seen as an approximation of progress.
+/// The information returned is good enough to build progress bars, but not exaxt enough to track
+/// individual download counts.
+/// 
+/// Also note that data is downloaded in bulk, which means that individual counters are unlikely
+/// to be updated one-by-one.
+public protocol SyncDownloadProgress: ProgressWithOperations {
+    /// Returns download progress towardss all data up until the specified `priority`
+    /// being received.
+    /// 
+    /// The returned ``ProgressWithOperations`` instance tracks the target amount of operations that
+    /// need to be downloaded in total and how many of them have already been received.
+    func untilPriority(priority: BucketPriority) -> ProgressWithOperations
+}
diff --git a/Sources/PowerSync/Protocol/sync/SyncStatusData.swift b/Sources/PowerSync/Protocol/sync/SyncStatusData.swift
index d4aa035..66b836a 100644
--- a/Sources/PowerSync/Protocol/sync/SyncStatusData.swift
+++ b/Sources/PowerSync/Protocol/sync/SyncStatusData.swift
@@ -11,6 +11,12 @@ public protocol SyncStatusData {
     /// Indicates whether the system is actively downloading changes.
     var downloading: Bool { get }
 
+    /// Realtime progress information about downloaded operations during an active sync.
+    /// 
+    /// For more information on what progress is reported, see ``SyncDownloadProgress``.
+    /// This value will be non-null only if ``downloading`` is `true`.
+    var downloadProgress: SyncDownloadProgress? { get }
+
     /// Indicates whether the system is actively uploading changes.
     var uploading: Bool { get }