Store an Object
Storing an object is the core action your app will perform. It is two steps: upload, which sends the data to the network, and pin, which saves the upload to the user's account. The SDK encrypts everything on the device before it leaves — the Understand what happened section after the example explains how.
Prerequisites
Before continuing, make sure you have:
- An App Key returned from a successful connection to an indexer.
Once you have established a successful connection, you’re ready to store your first object.
Example
use sia_storage::{app_id, AppKey, AppMetadata, Builder, Object, UploadOptions};
use std::io::{self, Write};
const INDEXER_URL: &str = "https://sia.storage";
const APP_META: AppMetadata = AppMetadata {
// Replace `app_id` with your real 32-byte App ID (hex-encoded, 64 chars).
// Generate this ONCE and keep it stable forever for your app.
id: app_id!("0000000000000000000000000000000000000000000000000000000000000000"),
name: "My App",
description: "Demo application",
service_url: "https://example.com",
logo_url: None,
callback_url: None,
};
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a builder that can reconnect using an existing App Key
let builder = Builder::new(INDEXER_URL, APP_META)?;
// Ask the user for their App Key
print!("Enter your App Key (hex): ");
io::stdout().flush()?;
let mut app_key_hex = String::new();
io::stdin().read_line(&mut app_key_hex)?;
let app_key_hex = app_key_hex.trim();
let mut seed = [0u8; 32];
hex::decode_to_slice(app_key_hex, &mut seed)?;
let app_key = AppKey::import(seed);
// Reconnect using the App Key
let sdk = match builder.connected(&app_key).await? {
Some(sdk) => sdk,
None => {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"invalid App Key",
)
.into())
}
};
println!("\nApp Connected!");
// Upload "Hello world!" from an in-memory reader
let reader = std::io::Cursor::new(b"Hello world!");
println!("\nStarting upload...");
let obj = Object::default();
let obj = sdk.upload(obj, reader, UploadOptions::default()).await?;
// Pin the object — without this, the upload is not persisted
sdk.pin_object(&obj).await?;
println!("\nUpload complete:");
println!(" - Size: {} bytes", obj.size());
println!(" - Object ID: {}", obj.id());
Ok(())
}package main
import (
"bufio"
"context"
"encoding/hex"
"fmt"
"os"
"strings"
"go.sia.tech/core/types"
"go.sia.tech/siastorage"
)
const indexerURL = "https://sia.storage"
// Replace this with your real 32-byte App ID (hex-encoded, 64 chars).
// Generate this ONCE and keep it stable forever for your app.
// Example: openssl rand -hex 32
const appIDHex = "0000000000000000000000000000000000000000000000000000000000000000"
// Parse the App ID once at startup.
var appID = func() (id types.Hash256) {
if err := id.UnmarshalText([]byte(appIDHex)); err != nil {
panic(err)
}
return
}()
func main() {
ctx := context.Background()
// Create a builder to manage SDK access.
builder := siastorage.NewBuilder(indexerURL, siastorage.AppMetadata{
ID: appID,
Name: "My App",
Description: "Demo application",
ServiceURL: "https://example.com",
})
// Ask the user for the App Key printed by connect-to-an-indexer.
fmt.Print("Enter your App Key (hex): ")
appKeyHex, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
panic(err)
}
appKeyHex = strings.TrimSpace(appKeyHex)
appKeySeed, err := hex.DecodeString(appKeyHex)
if err != nil {
panic(err)
}
if len(appKeySeed) != 32 {
panic("app key must be 32 bytes (64 hex chars)")
}
// Create an SDK instance with the stored App Key.
client, err := builder.SDK(types.NewPrivateKeyFromSeed(appKeySeed))
if err != nil {
panic(err)
}
defer client.Close()
fmt.Println("\nApp Connected!")
// Upload "Hello world!" from an in-memory reader.
fmt.Println("\nStarting upload...")
obj := siastorage.NewEmptyObject()
if err := client.Upload(ctx, &obj, strings.NewReader("Hello world!")); err != nil {
panic(err)
}
// Pin the object — without this, the upload is not persisted.
if err := client.PinObject(ctx, obj); err != nil {
panic(err)
}
fmt.Println("\nUpload complete:")
fmt.Println(" - Size:", obj.Size(), "bytes")
fmt.Println(" - Object ID:", obj.ID())
}import asyncio
from io import BytesIO
from sia_storage import (
Builder,
AppMetadata,
AppKey,
PinnedObject,
UploadOptions,
)
async def main():
# Configure your app identity details
meta = AppMetadata(
# Replace `appId` with your real 32-byte App ID (hex-encoded, 64 chars).
# Generate this ONCE and keep it stable forever for your app.
id=b"your-32-byte-app-id.............",
name="My App",
description="Demo application",
service_url="https://example.com",
logo_url=None,
callback_url=None
)
# Create a builder to manage the connection flow
builder = Builder("https://sia.storage", meta)
# Ask the user for their App Key
app_key_hex = input("\nEnter your App Key (hex): ").strip()
app_key = AppKey(bytes.fromhex(app_key_hex))
# Connect using the existing App Key
sdk = await builder.connected(app_key)
if sdk is None:
raise Exception(
"\nApp Key is not connected to this app on this indexer."
"\nRun connect-to-an-indexer.py first to approve and register the app."
)
print("\nApp Connected!")
# Upload "Hello world!"
print("\nStarting upload...")
reader = BytesIO(b"Hello world!")
obj = await sdk.upload(PinnedObject(), reader, UploadOptions())
# Pin the object — without this, the upload is not persisted
await sdk.pin_object(obj)
print("\nUpload complete:")
print(" - Size:", obj.size(), "bytes")
print(" - Object ID:", obj.id())
asyncio.run(main())import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:sia_storage/sia_storage.dart';
// Replace with your real 32-byte App ID (hex-encoded, 64 chars).
// Generate this ONCE and keep it stable forever for your app.
const appIdHex =
'0000000000000000000000000000000000000000000000000000000000000000';
Future<void> main() async {
final appMeta = AppMetadata(
id: Uint8List.fromList(hex.decode(appIdHex)),
name: 'My App',
description: 'Demo application',
serviceUrl: 'https://example.com',
);
final builder = await Sia.builder(
indexerUrl: 'https://sia.storage',
appMeta: appMeta,
);
// Ask the user for their App Key
stdout.write('Enter your App Key (hex): ');
final appKeyHex = stdin.readLineSync()?.trim() ?? '';
final appKey = await Sia.appKey(Uint8List.fromList(hex.decode(appKeyHex)));
// Reconnect using the App Key
final sdk = await builder.connected(appKey: appKey);
if (sdk == null) {
stderr.writeln(
'App Key is not connected to this app on this indexer.\n'
'Run connect-to-an-indexer.dart first to approve and register the app.',
);
exit(1);
}
print('\nApp Connected!');
// Upload "Hello world!" from an in-memory stream
print('\nStarting upload...');
final upload = sdk.upload(
object: PinnedObject(),
source: Stream.value(utf8.encode('Hello world!')),
);
final obj = await upload.result;
// Pin the object — without this, the upload is not persisted
await sdk.pinObject(object: obj);
print('\nUpload complete:');
print(' - Size: ${obj.size()} bytes');
print(' - Object ID: ${obj.id()}');
Sia.dispose();
}import { Builder, AppKey, PinnedObject, initSia } from '@siafoundation/sia-storage'
import { createInterface } from 'node:readline/promises'
import { stdin as input, stdout as output } from 'node:process'
// Initialize the SDK (loads the WASM module in browser environments).
await initSia()
const appMeta = {
// Replace `appId` with your real 32-byte App ID (hex-encoded, 64 chars).
// Generate this ONCE and keep it stable forever for your app.
id: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
name: 'My App',
description: 'Demo application',
serviceUrl: 'https://example.com',
}
const rl = createInterface({ input, output })
// Ask the user for their App Key
const appKeyHex = (await rl.question('\nEnter your App Key (hex): ')).trim()
const appKey = new AppKey(Buffer.from(appKeyHex, 'hex'))
// Reconnect using the App Key
const sdk = await new Builder('https://sia.storage', appMeta).connected(appKey)
if (!sdk) {
throw new Error(
'App Key is not connected to this app on this indexer.\n' +
'Run connect-to-an-indexer.js first to approve and register the app.',
)
}
console.log('\nApp Connected!')
// Upload "Hello world!" from an in-memory stream
console.log('\nStarting upload...')
const data = new Blob(['Hello world!']).stream()
const obj = await sdk.upload(new PinnedObject(), data)
// Pin the object — without this, the upload is not persisted
await sdk.pinObject(obj)
console.log('\nUpload complete:')
console.log(' - Size:', obj.size().toString(), 'bytes')
console.log(' - Object ID:', obj.id())
rl.close()import { Builder, AppKey, PinnedObject, initSia } from '@siafoundation/sia-storage'
// Initialize the SDK (loads the WASM module).
await initSia()
const appMeta = {
// Replace `appId` with your real 32-byte App ID (hex-encoded, 64 chars).
// Generate this ONCE and keep it stable forever for your app.
appId: '0000000000000000000000000000000000000000000000000000000000000000',
name: 'My App',
description: 'Demo application',
serviceUrl: 'https://example.com',
}
// Load the App Key from wherever you stored it during connect
const appKeyHex = localStorage.getItem('appKey')
if (!appKeyHex) {
throw new Error('No App Key stored. Run the Connect a Storage Account flow first.')
}
const appKey = new AppKey(Uint8Array.fromHex(appKeyHex))
// Reconnect using the App Key
const sdk = await new Builder('https://sia.storage', appMeta).connected(appKey)
if (!sdk) {
throw new Error('App Key is not connected to this app on this indexer.')
}
console.log('App Connected!')
// Upload "Hello world!" from an in-memory stream
console.log('Starting upload...')
const data = new Blob(['Hello world!']).stream()
const obj = await sdk.upload(new PinnedObject(), data)
// Pin the object — without this, the upload is not persisted
await sdk.pinObject(obj)
console.log('Upload complete:')
console.log(' - Size:', obj.size(), 'bytes')
console.log(' - Object ID:', obj.id())Understand what happened
What the SDK did with your data:
How the upload was secured
- All data was encrypted client-side before it left the device.
- The encrypted data was erasure-coded into multiple redundant shards.
- Each shard was uploaded to an independent storage provider located across the globe.
- The indexer stored only the encrypted object record. It never sees plaintext data.
Encryption keeps data private even if intercepted. Erasure coding keeps it recoverable even if some hosts go offline.
Upload vs. pin
Uploading returns an object handle you can work with immediately (for example, to pin it, share it, or download it later).
In this quickstart flow, upload and pin are separate steps:
- Upload sends shards to storage providers and builds the object’s layout.
- Pinning saves the upload to the user’s account: it persists the sealed object record in the indexer and pins the underlying slabs so the object becomes listable, syncable, and eligible for repair.
Pinning encrypts and signs the object (a process called sealing) before sending it to the indexer — you don’t need to seal manually. See Pinning and Objects for more.
The Object ID
The Object ID is a permanent, 32-byte identifier derived from the object’s slab layout, so you can read it directly after upload — and you’ll use it to look the object up and download it later.
Metadata is application-defined and encrypted. See the Object Metadata recipe for details.
