> ## Documentation Index
> Fetch the complete documentation index at: https://filekit.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Bookmark Data

> Maintain persistent access to files across app restarts

<Check>Available on Android, iOS, macOS, and JVM targets</Check>

## The Problem: Losing File Access

Modern operating systems use security measures like sandboxing, which means your app can lose access to files selected by the user once it restarts. A standard file path might become invalid. This is especially true on Android (for files outside your app's private storage) and on sandboxed apps on macOS and iOS.

## The Solution: Bookmark Data

`BookmarkData` is a feature that creates a persistent, secure reference to a file. You can save this reference and use it later to reliably regain access to the file, even after your app has been closed and reopened. FileKit handles the complex platform-specific implementations for you.

## The Basic Workflow

The process involves two main steps: creating and saving a bookmark, and later loading and resolving it.

```kotlin theme={null}
// 1. User picks a file
val userPickedFile: PlatformFile = // ...from a file picker

// 2. Create and save its bookmark data
val bookmark = userPickedFile.bookmarkData()
MyPreferences.save("last_file_bookmark", bookmark.bytes)

// --- App restarts ---

// 3. Load the saved bookmark data
val savedBytes = MyPreferences.load("last_file_bookmark")

// 4. Restore the PlatformFile from the bookmark
if (savedBytes != null) {
    val restoredFile = PlatformFile.fromBookmarkData(savedBytes)
    // Now you can work with the restoredFile
}
```

`fromBookmarkData()` remains a supported, non-deprecated convenience API when you only need the restored file. Use `resolveBookmarkData()` when your application also needs refresh metadata or can update its stored bookmark:

```kotlin theme={null}
val resolution = PlatformFile.resolveBookmarkData(savedBytes)
val restoredFile = resolution.file

if (resolution.shouldRefresh) {
    // Replace the bytes in your own storage.
    val refreshedBookmark = restoredFile.bookmarkData()
    MyPreferences.save("last_file_bookmark", refreshedBookmark.bytes)
}
```

* `isStale` means the operating system resolved the bookmark but reported that its native representation should be recreated.
* `shouldRefresh` is FileKit's broader recommendation. It is also `true` when FileKit successfully resolves bookmark data written by an older FileKit version.

Refreshing is advisory. The old bookmark may still work, and refresh can fail if the resource disappeared or macOS already revoked access. In that case, ask the user to select the file or directory again.

## Complete Example

Here’s a more complete, practical example using a simple object to manage the bookmark.

<CodeGroup>
  ```kotlin Storage theme={null}
  // A simple manager for a single bookmarked file
  object BookmarkManager {
      private val bookmarkFile = FileKit.filesDir / "bookmark.bin"

      suspend fun save(file: PlatformFile) {
          try {
              val bookmark = file.bookmarkData()
              bookmarkFile.write(bookmark.bytes)
          } catch (e: Exception) {
              // Handle exceptions, e.g., log the error
              println("Error saving bookmark: ${e.message}")
          }
      }

      suspend fun load(): PlatformFile? {
          if (!bookmarkFile.exists()) return null
          
          return try {
              val bytes = bookmarkFile.readBytes()
              val file = PlatformFile.fromBookmarkData(bytes)
              
              // Best practice: verify the file still exists
              if (file.exists()) {
                  file
              } else {
                  // The file was moved or deleted, so clean up the stale bookmark
                  clear()
                  null
              }
          } catch (e: Exception) {
              // Bookmark is invalid or corrupted, clean it up
              clear()
              null
          }
      }
      
      suspend fun clear() {
          try {
              if (bookmarkFile.exists()) {
                  bookmarkFile.delete()
              }
          } catch (e: Exception) {
              println("Error clearing bookmark: ${e.message}")
          }
      }
  }
  ```

  ```kotlin Compose UI theme={null}
  // Example usage in a Composable screen
  @Composable
  fun MyScreen() {
      var file by remember { mutableStateOf<PlatformFile?>(null) }
      val coroutineScope = rememberCoroutineScope()
      
      // Load the file when the screen is first composed
      LaunchedEffect(Unit) {
          file = BookmarkManager.load()
      }
      
      val picker = rememberFilePickerLauncher(
          onError = { failure -> println("Picker failed: ${failure.message}") },
          onResult = { pickedFile ->
              file = pickedFile
              // Save the bookmark in a coroutine
              pickedFile?.let {
                  coroutineScope.launch {
                      BookmarkManager.save(it)
                  }
              }
          },
      )
      
      // UI to show file details and a button to launch the picker
      Column {
          if (file != null) {
              Text("Current file: ${file?.name}")
          }
          Button(onClick = { picker.launch() }) {
              Text("Pick a File")
          }
      }
  }
  ```
</CodeGroup>

## Platform-Specific Behavior

FileKit abstracts away the details, but here's what happens on each platform:

* **Android**: For standard file paths, the path itself is stored. For `content://` URIs from the system picker, FileKit requests persistent URI permissions and stores the URI string. This ensures long-term access.

* **iOS**: Uses Foundation bookmark data and keeps the security-scoped URL behavior supplied by the system document picker. The explicit persistent bookmark flags described below are macOS-only.

* **Kotlin/Native macOS**: Uses a native macOS bookmark. FileKit automatically creates an explicit security-scoped bookmark when the running application adopts App Sandbox and a regular native bookmark otherwise.

* **JVM macOS**: Uses the same versioned native bookmark format through CoreFoundation. A `PlatformFile` restored from a security-scoped bookmark retains its access capability, including for children inside a bookmarked directory.

* **Kotlin/Native Linux, JVM Linux, and Windows**: Stores the file path. These platforms use a path-based bookmark representation.

New macOS bookmark data is wrapped in a versioned FileKit format. Existing unwrapped Kotlin/Native bookmarks and JVM path bytes remain readable. Bookmark bytes are platform-specific and must not be treated as portable between operating systems or applications.

## macOS App Sandbox Setup

Persistent access to user-selected locations outside a macOS application's container requires appropriate signing entitlements. Configure the packaged application with:

```xml theme={null}
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
```

FileKit reads the signed App Sandbox entitlement at runtime and selects security-scoped bookmark creation automatically. When sandboxing is enabled, failure to create scoped bookmark data is reported instead of silently falling back to a bookmark that cannot restore access across launches.

Keep security-scoped access balanced. FileKit does this around its own file operations and keeps access active until returned sources and sinks are closed. Use `withScopedAccess` when passing a restored file to another library:

```kotlin theme={null}
restoredFile.withScopedAccess { file ->
    thirdPartyLibrary.open(file.path)
}
```

Call `releaseBookmark()` when the restored bookmark is no longer needed. It prevents new scoped operations, which then fail with `FileKitException`, while allowing already-open sources and sinks to close cleanly.

## Handling Invalid and Legacy Bookmarks

A bookmark is not a guarantee. It can become invalid if the original file is deleted, moved, or if permissions change. A stale bookmark is different: it resolved successfully but should be recreated. Legacy bookmark data is also different: FileKit resolved an older representation and recommends replacing it.

<Warning>
  **Always handle restoration failures gracefully.** A bookmark can become invalid if:

  * The user moves or deletes the file.
  * The user revokes file permissions for your app.
  * System security policies change.
  * The app is uninstalled and reinstalled (on some platforms).
</Warning>

Your code should anticipate that restoring from a bookmark might fail.

```kotlin theme={null}
suspend fun loadFileSafely(): PlatformFile? {
    return try {
        val bytes = MyStorage.getBookmarkBytes() ?: return null
        val file = PlatformFile.fromBookmarkData(bytes)

        // The most important check: does the file still exist?
        if (file.exists()) {
            file
        } else {
            // The file is gone. Clean up the invalid bookmark.
            MyStorage.deleteBookmark()
            null
        }
    } catch (e: Exception) {
        // The bookmark data is corrupted or invalid for other reasons.
        // Clean it up to prevent future errors.
        MyStorage.deleteBookmark()
        null
    }
}
```

This defensive approach ensures your app doesn't crash from an invalid bookmark and can self-heal by clearing invalid data.
