Skip to content

Upload an Object

Uploading data is the core action your app will perform on the Sia network. When you upload a file through the SDK, the process is made secure by design:

  • All data is encrypted client-side by the user before it leaves the device.
  • Data is erasure-coded into multiple redundant shards.
  • Each shard is uploaded to independent storage providers located across the globe.
  • The indexer stores only encrypted metadata and helps coordinate uploads, downloads, and object management.

This means that even if some hosts go offline—or a malicious actor attempts to intercept user data—all data will remain private and recoverable.

Prerequisites

Before continuing, make sure you have:

Once you have established a successful connection, you’re ready to upload your first object.

Example

import asyncio
import json
from io import BytesIO

from indexd_ffi import (
    generate_recovery_phrase,
    uniffi_set_event_loop,
    Builder,
    AppMeta,
    UploadOptions,
    Reader,
    UploadProgressCallback,
)

# Reader helper
class BytesReader(Reader):
    def __init__(self, data: bytes, chunk_size: int = 65536):
        self.buffer = BytesIO(data)
        self.chunk_size = chunk_size

    async def read(self) -> bytes:
        # When the buffer is exhausted, this returns b"" (EOF).
        return self.buffer.read(self.chunk_size)

# Progress callback is optional and can be used to monitor the progress of the upload
class PrintProgress(UploadProgressCallback):
    def progress(self, uploaded: int, encoded_size: int) -> None:
        if encoded_size == 0:
            print("Starting upload…")
            return
        percent = (uploaded / encoded_size) * 100
        print(f"Upload progress: {percent:.1f}% ({uploaded}/{encoded_size} bytes)")

async def main():
    # IMPORTANT: required for UniFFI async trait callbacks (Reader/Writer/etc.)
    uniffi_set_event_loop(asyncio.get_running_loop())

    # Create a builder to manage the connection flow
    builder = Builder("https://app.sia.storage")

    # Configure your app identity details
    meta = AppMeta(
        id=b"your-32-byte-app-id.............",
        name="My App",
        description="Demo application",
        service_url="https://example.com",
        logo_url=None,
        callback_url=None
    )

    # Request app connection and get the approval URL
    builder = await builder.request_connection(meta)
    print("Open this URL to approve the app:", builder.response_url())

    # Wait for the user to approve the request
    try:
        builder = await builder.wait_for_approval()
    except Exception as e:
        raise Exception("\nApp was not approved (rejected or request expired)") from e

    # Ask the user for their recovery phrase
    recovery_phrase = input("\nEnter your recovery phrase (type `seed` to generate a new one): ").strip()

    if recovery_phrase == "seed":
        recovery_phrase = generate_recovery_phrase()
        print("\nRecovery phrase:", recovery_phrase)

    # Register an SDK instance with your recovery phrase.
    sdk = await builder.register(recovery_phrase)

    # The App Key should be exported and stored securely for future launches, but we don't demonstrate storage here.
    app_key = sdk.app_key()
    print("\nApp Key export (persist however your app prefers):", app_key.export())

    print("\nApp Connected!")

    #-------------------------------------------------------
    # UPLOAD AN OBJECT
    #-------------------------------------------------------

    # Configure Upload Options
    upload_options = UploadOptions(
        # Progress callback is optional and can be used to monitor the progress of the upload
        progress_callback=PrintProgress()
    )

    # Upload the "Hello world!" data
    print("\nStarting upload...")
    reader = BytesReader(b"Hello world!")
    obj = await sdk.upload(reader, upload_options)

    # Attach optional application metadata (encrypted before the indexer sees it).
    # NOTE: update_object_metadata() requires a pinned object, so we set metadata before pinning.
    obj.update_metadata(json.dumps({"File Name": "example.txt"}).encode())

    # IMPORTANT: upload returns an object whose slabs are not yet pinned in the indexer.
    # Pinning persists the sealed object + pins its slabs (including the metadata set above).
    await sdk.pin_object(obj)

    sealed = obj.seal(app_key)
    print("\nObject Sealed:")
    print(" - Sealed ID:", sealed.id)

    print("\nUpload complete:")
    print(" - Size:", obj.size(), "bytes")

asyncio.run(main())

🚧 Coming soon

🚧 Coming soon

🚧 Coming soon

🚧 Coming soon

🚧 Coming soon

🚧 Coming soon

Deep Dive

Objects & Metadata

await sdk.upload(reader, upload_options) uploads your encrypted, erasure-coded data and returns an object handle you can work with immediately (e.g., seal it, share it, download it).

In this quickstart flow, upload and pin are separate steps:

  • Upload sends shards to storage providers and builds the object’s layout.
  • Pinning (await sdk.pin_object(obj)) persists the sealed object record in the indexer and pins the underlying slabs so the object becomes listable/syncable and eligible for repair.

Metadata is application-defined and encrypted. In this guide we set metadata on the object (obj.update_metadata(...)) before pinning so the pinned record includes it.

Streaming vs Single-Write

Uploads are Reader-based. The SDK repeatedly calls your Reader.read() method until it returns b"" (EOF).

In the example, BytesReader wraps an in-memory buffer:

reader = BytesReader(b"Hello world!")
obj = await sdk.upload(reader, upload_options)

In a real app (especially for large files), you typically implement a Reader helper that reads from a file in chunks. The core idea is the same: return the next chunk each call, and return b"" when finished.

Progress Callback

The progress_callback runs while data is being uploaded:

  • It receives uploaded and encoded_size in bytes.
  • It may be called multiple times as data is sent.
  • It’s safe to:
    • Update a progress bar
    • Log percentages
    • Trigger UI updates

Common Practices

Upload from a file

Implement a Reader that reads from a file in chunks:

import json
from indexd_ffi import Reader

class BytesReader(Reader):
    def __init__(self, path: str, chunk_size: int = 65536):
        self.f = open(path, "rb")
        self.chunk_size = chunk_size

    async def read(self) -> bytes:
        chunk = self.f.read(self.chunk_size)
        if chunk == b"":
            self.f.close()
        return chunk

reader = BytesReader("example.txt")
obj = await sdk.upload(reader, upload_options)
obj.update_metadata(json.dumps({"File Name": "example.txt"}).encode())
await sdk.pin_object(obj)

For GUI apps or high-throughput workloads, you may prefer async file IO or reading in a background thread — but the Reader contract stays the same.

Custom metadata

Store original filename, MIME type, user ID, or application-specific tags.

Real progress bars

Instead of printing percentages, integrate progress_callback with a CLI or GUI progress bar.

Next Step

Share an Object →