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

# File Storage

> Take photos, pick files, upload to the cloud, and cache for instant display. Permission prompts and offline handling are written for you.

Most apps need to handle files: a profile photo, a receipt, a document, a video. Runable wires up the whole flow  ask for the camera, pick from the library, upload in the background, cache for fast display next time, and resume uploads after a network drop.

## What you can do

<CardGroup cols={2}>
  <Card title="Take a photo or video" icon="camera">
    Open the camera in-app. Pick still photos, square crops, video clips, or scan a barcode. Front and back camera, flash, and tap-to-focus all supported.
  </Card>

  <Card title="Pick from the library" icon="image">
    Choose one or many photos and videos. The system picker on iOS 14+ and Android 13+ shows only what the user grants, so they keep their photo library private.
  </Card>

  <Card title="Pick any file" icon="file">
    PDFs, spreadsheets, audio, anything else. The user picks from Files, Google Drive, iCloud Drive, Dropbox  whatever they have set up on their phone.
  </Card>

  <Card title="Upload in the background" icon="cloud-upload">
    Big uploads continue when the user backgrounds the app. Resume automatically if the connection drops. Progress shows in your UI.
  </Card>

  <Card title="Cache for instant display" icon="bolt">
    Once a remote image or file has been downloaded, the next view is instant from the local cache. No spinner, no flash of placeholder.
  </Card>

  <Card title="Share content out" icon="share">
    Open the system share sheet so users send files to Messages, Mail, Notes, or any other app on their phone.
  </Card>
</CardGroup>

## How file flow works

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#e6fafb', 'primaryTextColor': '#1a1a2e', 'primaryBorderColor': '#01b7cb', 'lineColor': '#01b7cb', 'fontFamily': 'Inter, sans-serif', 'fontSize': '14px' }}}%%
flowchart LR
    A("User picks a file"):::step --> B("App previews"):::step
    B --> C("Upload to cloud storage"):::step
    C --> D("Server returns a URL"):::step
    D --> E("Phone caches for next view"):::step
    classDef step fill:#e6fafb,stroke:#01b7cb,stroke-width:2px,rx:12,ry:12,color:#1a1a2e
```

## Add file handling to your app

<Steps>
  <Step title="Describe what you want users to do with files">
    *Profile photo upload: tap the avatar to open a sheet with "Take photo" and "Pick from library." Image is cropped to a square, compressed to a max of 2 MB, uploaded, and shown as the avatar everywhere immediately.*

    Or:

    *Add a receipts feature: a list of uploaded receipts with date, vendor, amount, and a thumbnail. Tap the plus button to take a photo of a new receipt; the app uses OCR to fill in the vendor and amount fields, which the user can edit before saving.*

    The agent builds the flow, the permissions, the upload, and the display.
  </Step>

  <Step title="Write the permission prompts">
    On first use, the system asks for camera and photo library access. The reason string shown in the prompt is set by your app  Apple and Google require it to be clear and specific.

    The agent generates sensible defaults:

    > "Cairn uses your camera to take photos of your habits."
    >
    > "Cairn uses your photo library so you can pick existing photos."

    Override either with: *change the camera permission prompt to "Cairn needs camera access so you can scan your books' barcodes."*

    <Warning>
      The system prompt only fires once. If the user declines, you have to walk them through Settings to flip it back on. The agent generates a polite in-app screen that explains why the feature needs the permission and links to Settings.
    </Warning>
  </Step>

  <Step title="Compress before uploading">
    A raw phone photo can be 510 MB. For most apps that is wasteful. The agent compresses images by default:

    * **Avatars**: square crop, 500×500, target 100 KB.
    * **Photo posts**: resize to 1080px on the long side, target 500 KB.
    * **Document scans**: keep aspect, target 1 MB, prefer sharpness.

    Ask for a different target: *keep photo uploads at original resolution; use 2 MB as the compression target*.
  </Step>

  <Step title="Upload">
    Tapping save kicks off the upload. The agent shows a progress indicator in the spot where the file will end up (a spinner over the avatar, a progress bar on the receipt row). The upload continues even if the user navigates away or backgrounds the app.

    If the connection drops mid-upload, the upload resumes from where it left off when the network comes back. The user does not have to retry.
  </Step>

  <Step title="Display next time">
    Once uploaded, the file lives in cloud storage and your app references it by URL. The next time anyone views the same image, the phone serves it from a local cache  no network call, no spinner.

    The cache is bounded (the agent defaults to about 200 MB per app) and evicts the least-recently-used files when full. Users never have to "clear cache" manually.
  </Step>
</Steps>

## Image transformations

The agent can resize, crop, or compress images on the fly without re-uploading.

| Transform      | What it does                           | When to use                               |
| -------------- | -------------------------------------- | ----------------------------------------- |
| `width/height` | Resize to fit, preserving aspect ratio | Showing thumbnails of large images        |
| `crop`         | Fill a box, cropping the longer side   | Square avatar from a portrait photo       |
| `quality`      | Compress to a target quality           | Saving bandwidth on data-light screens    |
| `format`       | Convert between JPEG, PNG, WebP        | Smaller files for the same visual quality |
| `blur`         | Gaussian blur                          | Loading placeholders, sensitive content   |

Ask: *show thumbnails on the list page at 200×200 cropped and the full image at 1080 wide on the detail page*. The same source URL serves both  no extra upload, no extra storage.

## Camera features

For apps that use the camera as the main input, ask for the specific behavior you need:

<CardGroup cols={2}>
  <Card title="Barcode scanning" icon="scan">
    Scan ISBN, QR, UPC, Code 128, and most others. The agent overlays a viewfinder rectangle and fires a haptic on a confirmed scan.
  </Card>

  <Card title="Document scan" icon="file-text">
    Detect a paper's edges, square it up, and produce a cropped PDF or image. Multi-page support.
  </Card>

  <Card title="Text recognition" icon="text-cursor-input">
    OCR  pull text out of a photo. Handles printed and handwritten text, multiple languages, vertical alignment.
  </Card>

  <Card title="Custom overlay" icon="camera-off">
    A custom camera screen with your own brand, guides, and capture button. Useful for product photos, before/after shots, ID verification.
  </Card>
</CardGroup>

## Share content out

Users often want to send a file out of your app. The system share sheet handles every destination on the phone  Messages, Mail, Notes, Drive, Slack, anything they have installed.

Ask: *add a share button on each receipt that lets the user send the image and a one-line summary via the system share sheet*. The agent wires up the share content and the right title for the share sheet.

## What you can see

The project dashboard surfaces file storage stats:

* **Storage used** total, and per user
* **Upload success rate**  what percentage of upload attempts succeed
* **Average upload time** by file size bucket
* **Cache hit rate**  how often the phone served from cache instead of fetching
* **Largest 100 files**  useful for spotting users who are abusing the storage

## What you cannot do

* **Access photos without the user's permission.** Both stores require an explicit permission grant. There is no workaround.
* **Upload arbitrarily large files from a phone in one go.** Mobile networks cap practical uploads at around 11.5 GB per file. For larger files, the agent chunks the upload and resumes between sessions.
* **Read other apps' files.** The phone sandboxes each app's storage. You only see what the user explicitly hands to your app via the picker.
* **Persist content beyond the user's account.** When a user deletes their account, their files are deleted too. There is no path to keep files after deletion.

## Next steps

<CardGroup cols={2}>
  <Card title="Mobile App Data" icon="database" href="/mobile-app-database">
    Pair files with structured data on the device and server.
  </Card>

  <Card title="Push Notifications" icon="bell" href="/mobile-app-push-notifications">
    Notify the user when a file upload finishes in the background.
  </Card>

  <Card title="Environment Variables" icon="key" href="/mobile-app-environment-variables">
    Switch storage endpoints between dev, preview, and production.
  </Card>

  <Card title="Analytics" icon="bar-chart-3" href="/mobile-app-analytics">
    Watch upload success rates and cache performance in production.
  </Card>
</CardGroup>
