Data Storage Using Walrus
Walrus is a decentralized storage and data availability protocol built specifically for large binary files, or blobs. It uses Sui for coordination, payment, and governance, making it a natural storage layer for apps built on Sui.
Each section explains a Walrus concept or operation and shows where it appears in the encrypted social media example OnlyFins. OnlyFins is a demo content platform where creators post images stored on Walrus, some encrypted with Seal. Each post is a Sui object. Users request access onchain to unlock encrypted posts, demonstrating how Sui, Walrus, and Seal work together as a stack.
For patterns OnlyFins does not cover, the guide draws on Walrus Onboarding modules and walrus-pocs, a collection of focused Walrus proof-of-concept examples maintained by Mysten Labs. The Walrus SDK documentation is the authoritative reference for all SDK options. The Encryption with Seal guide builds on this same app.
Architecture
Blob storage
Walrus stores data as blobs, which are arbitrary binary objects. All blobs stored on Walrus are public and discoverable by anyone.
Both blob contents and their metadata are public. Blob IDs, blob attributes, and the onchain blob registration are all discoverable by anyone, so an attacker can enumerate and read everything you store. Never store secrets or private data unencrypted. For sensitive content, encrypt before upload using Seal or envelope encryption, and treat blob IDs and attributes as public. See Data encryption and the Security Best Practices guide.
Walrus uses erasure coding to distribute encoded parts of each blob across all storage nodes, maintaining storage costs at approximately 5 times the size of the original blob, according to the Walrus documentation. This is significantly more cost-efficient than full replication and more resilient than partial replication.
The WalrusFile API groups multiple files into a single blob when storing them together, which is more storage-efficient than uploading each file separately.
What is the difference between a blob ID and an object ID?
Every blob has 2 identifiers:
- Blob ID: A content-addressed hash of the blob data, computed offchain and deterministic from the blob's contents.
- Object ID: The Sui object ID of the onchain blob registration record. Because blobs are Sui objects, addresses can own them, and you can transfer them, wrap them in other objects, or manage them through Sui smart contracts.
In OnlyFins, the image_blob_id field on each Post Sui object stores the blob ID. The frontend uses this to construct a fetch URL. The object ID lets the app manage blob lifecycle onchain, the same way it manages any other Sui object.
Storage nodes
Storage nodes are the servers that store encoded blob data. When you write a blob, the Walrus client distributes encoded shards across the active storage node committee. When you read a blob, the client fetches enough shards to reconstruct the original data. Writing a blob directly to storage nodes requires approximately 2,200 requests. Reading requires approximately 335 requests. These counts are approximate figures from the Walrus documentation, not fixed values.
Upload relay
The upload relay is a server-side proxy that accepts blob data from a client and handles the distribution to storage nodes. Using an upload relay significantly reduces the number of requests a client must make to write a blob. This is especially important in browser environments where issuing thousands of direct requests to storage nodes is not practical. Reads through the Walrus SDK still require many requests regardless of whether you use an upload relay. The upload relay is part of the write path only; to read blobs over HTTP, use an aggregator.
Publisher
A publisher is an HTTP server that accepts blob data over a REST API and handles the entire Walrus write flow on your behalf. Publishers are the most direct way to integrate uploads in server-side applications without using the TypeScript SDK (TS SDK).
For production environments, use the send-object-to parameter when uploading through a publisher. This instructs the publisher to transfer the resulting blob object to a specified Sui address after upload, so you retain ownership of blobs stored through a shared publisher.
Aggregator
An aggregator is an HTTP server that serves blobs over a standard REST API. Aggregators are the most direct way to read Walrus data without using the TS SDK, and they work well with standard caches and CDNs.
OnlyFins uses an aggregator to serve all images in the frontend. OnlyFins builds all image fetches from a single constant, the aggregator base URL:
frontend/src/constants.ts. You probably need to run `pnpm prebuild` and restart the site.For most apps, publishers and aggregators are the recommended integration path because they abstract away all storage node communication. The Walrus documentation covers running and configuring publishers and aggregators. The TS SDK is the right tool when your app needs direct interaction with Walrus or when users need to pay for their own storage.
Tooling
Two tools cover most Walrus integration scenarios.
CLI
The Walrus CLI provides direct access to all Walrus operations from the command line, including storing, reading, and managing blobs. OnlyFins uses the CLI in its seeding workflow to upload images before registering them onchain. See the Walrus CLI documentation for setup and usage.
TS SDK
The Walrus TS SDK (@mysten/walrus) extends the Sui TypeScript SDK client with Walrus-specific methods. It exposes high-level methods for reading and writing blobs, as well as lower-level methods for individual steps when you need more control.
The TS SDK is most useful when:
- Users need to pay for their own storage directly through their wallets.
- Your app needs to interact directly with Walrus storage nodes or an upload relay.
- You need full control over the registration, upload, and certification flow.
Install the SDK:
$ npm install --save @mysten/walrus @mysten/sui
Set up the client by extending a SuiGrpcClient with the Walrus plugin:
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { walrus } from '@mysten/walrus';
const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443',
}).$extend(walrus());
The Walrus SDK documentation is the single source of truth for all SDK patterns, options, and examples.
Some code examples in this guide use SuiClient from @mysten/sui/client rather than SuiGrpcClient from @mysten/sui/grpc. SuiGrpcClient is the recommended transport for new integrations. In current SDK versions, all Walrus SDK and transaction APIs are identical regardless of which client you use. See the Sui SDK v2 migration guide for upgrade instructions.
For React apps, wrap your component tree with a context provider that initializes the client once and makes it available throughout the app. The walrus-pocs repo includes focused examples for each SDK operation that show this pattern in a clean Node.js context. For a React-specific provider, initialize SuiGrpcClient.$extend(walrus()) in your app's root and pass the result through React context using the same pattern as the client setup example in TS SDK.
How do I upload data to Walrus?
There are 2 primary ways to upload data to Walrus: using the CLI directly, or using the TS SDK programmatically. The TS SDK offers 2 upload APIs: writeBlob for raw blobs, and the WalrusFile API which stores files efficiently by grouping them together.
Using the TS SDK with a Signer
Use writeBlob with a Signer instance for server-side uploads where a backend script or job controls the wallet. The Walrus Onboarding Module 07 provides 2 clean examples: a direct upload and an upload through the relay. Both use the $extend(walrus()) pattern:
07-Walrus-SDK-upload-relay/hands-on-source-code/src/examples/basic-upload-example.ts. You probably need to run `pnpm prebuild` and restart the site.To reduce the number of requests from approximately 2,200 to a single relay request, configure the upload relay when extending the client:
07-Walrus-SDK-upload-relay/hands-on-source-code/src/examples/basic-upload-example.ts. You probably need to run `pnpm prebuild` and restart the site.In OnlyFins, the signer pattern appears in the post registration step. After uploading images with the CLI and recording the blob IDs, createPosts.ts uses a Keypair signer to build and execute a programmable transaction block (PTB) that registers each post onchain with its blob ID. The signer setup in config.ts shows the pattern for loading Ed25519Keypair instances from environment variables:
backend/src/config.ts. You probably need to run `pnpm prebuild` and restart the site.With signers configured, createPosts.ts builds a PTB that calls posts::create_post for each post, embedding the Walrus blob ID as a field on the resulting Post Sui object:
backend/src/createPosts.ts. You probably need to run `pnpm prebuild` and restart the site.Using the TS SDK with user wallets
When a user wallet signs uploads in a browser, wallets use popups that browsers block if the popups do not open in direct response to a user interaction. The writeFilesFlow method breaks the upload into discrete steps, each triggered by a separate user action.
The flow has 5 steps:
encode: Encode the files and compute a blob ID.register: Return a transaction that registers the blob onchain.upload: Upload data to storage nodes or the upload relay.certify: Return a transaction that certifies the blob onchain.listFiles: Return the list of created files.
There is currently no example of this pattern in OnlyFins. Two external references cover it: relay.wal.app is a deployed demo with an open source repository that shows a complete production-ready browser upload flow. The write-from-wallet example in the TS SDK repo is a more concise undeployed version. Both use writeFilesFlow with @mysten/dapp-kit-react.
The walrus-pocs SDK example shows the core writeBlob pattern with a signer:
sdk/src/write.ts. You probably need to run `pnpm prebuild` and restart the site.For browser wallet upload flows using writeBlobFlow and writeFilesFlow, see the write-from-wallet example in the TS SDK repo, which shows the complete dApp Kit integration pattern.
Using the WalrusFile API to batch upload
When uploading multiple files together, use writeFiles with WalrusFile.from() to attach identifiers and tags to each file. Storing files together is more efficient than uploading them separately:
11-Batch-storage/hands-on-source-code/03-creation-process/ts/03-create-simple.ts. You probably need to run `pnpm prebuild` and restart the site.How do I read data from Walrus?
Reading assumes you know the ID of the data you want to retrieve. In OnlyFins, Post Sui objects store blob IDs as fields, and the app discovers them by querying the blockchain. For how to discover blobs you own dynamically, see Querying blobs.
Finding the blobs you want to read
In OnlyFins, the Feed component fetches a known set of post object IDs from Sui using useSuiClientQuery with multiGetObjects. This is the same pattern you use to fetch any Sui object by ID:
frontend/src/components/Feed.tsx. You probably need to run `pnpm prebuild` and restart the site.The transformSuiObjectsToPosts utility extracts the image_blob_id field from each Sui object and constructs the aggregator URL. For encrypted posts it also extracts the encryption_id field used in the decryption flow:
frontend/src/utils/post-transform.ts. You probably need to run `pnpm prebuild` and restart the site.Reading metadata and attributes
Blobs support custom key-value attributes stored as dynamic fields on the Sui blob object. Attributes can include content type, content length, or any app-specific metadata. You set them at upload time or update them afterward. They travel with the blob object on Sui, and you can read them alongside the blob content.
The attributes.ts example in the TS SDK shows all 3 operations in one script: setting attributes at upload through the attributes parameter on writeBlob, reading them with readBlobAttributes, and updating or deleting individual keys with executeWriteBlobAttributesTransaction:
packages/walrus/examples/basics/attributes.ts. You probably need to run `pnpm prebuild` and restart the site.Using the WalrusFile API
The WalrusFile API is the recommended way to read Walrus data with the TS SDK. The getFiles method accepts both individual blob IDs and IDs of batches stored together, and returns WalrusFile instances that work like Response objects from the fetch API. Reading files in batches is more efficient when loading multiple files that you stored together because the client can optimize requests.
The Walrus Onboarding Module 11 shows how to retrieve files by identifier:
11-Batch-storage/hands-on-source-code/04-retrieval-process/ts/01-get-files-identifiers.ts. You probably need to run `pnpm prebuild` and restart the site.The walrus-pocs SDK example shows how to read a blob by ID and write it to disk:
sdk/src/download.ts. You probably need to run `pnpm prebuild` and restart the site.Reads through the Walrus SDK require approximately 335 requests to storage nodes regardless of whether you use an upload relay. For high-read-volume scenarios, aggregator-based reads are significantly more efficient.
Reading with an aggregator
OnlyFins reads all images using plain HTTP fetches to the aggregator. The fetchFromWalrus utility wraps the fetch with retry logic and a 25-second timeout:
frontend/src/utils/walrus-fetch.ts. You probably need to run `pnpm prebuild` and restart the site.For unencrypted posts, the frontend uses the aggregator URL from transformSuiObjectsToPosts directly as an image source. For encrypted posts, fetchFromWalrus retrieves the encrypted bytes before passing them to Seal for decryption.
When reading directly with the TS SDK, the Walrus Onboarding Module 07 shows readBlob with proper typed error handling for the most common failure modes:
07-Walrus-SDK-upload-relay/hands-on-source-code/src/examples/basic-download-example.ts. You probably need to run `pnpm prebuild` and restart the site.Managing blobs
Because blobs are Sui objects, you manage them using the same tools and patterns you use for any other Sui object. The most common blob management operations are querying blobs, sharing blobs, deleting blobs, and extending the lifetime of blobs.
Querying blobs
Individual wallet addresses can own blobs, or other Sui objects can wrap them. You query personally owned blobs exactly like any owned Sui object type, using getOwnedObjects with a StructType filter on the Walrus blob type.
OnlyFins queries a fixed hardcoded list of post objects rather than dynamically discovering blobs by owner. To add a view that displays all blobs the connected wallet owns, deploy a version of Sui dApp Kit's react-client-dapp starter and add an object type filter to the owned-objects query. The result looks like this:
const { data } = useSuiClientQuery('getOwnedObjects', {
owner: currentAccount.address,
filter: {
StructType: `${WALRUS_PACKAGE_ID}::blob::Blob`,
},
options: { showContent: true },
});
In the filter, replace WALRUS_PACKAGE_ID with the ID of the Walrus package on the network you are using.
When displaying blob owners, resolve their SuiNS name if one exists rather than showing a raw address. Use useResolveSuiNSName from Sui dApp Kit, which returns the primary SuiNS name for any address, and fall back to a truncated hex string when no name is set.
Sharing blobs
Walrus provides a standard for wrapping a blob in a shared object. Anyone, not just the original owner, can fund the shared object and extend its lifetime. This is useful for content that multiple parties want to keep available.
You can currently share blobs only through the Walrus CLI or manually through Move.
Deleting blobs
You must mark blobs as deletable: true at registration time to make them eligible for deletion. You cannot delete permanent blobs.
The walrus-pocs SDK example shows how to find a blob object by ID and execute a delete transaction:
sdk/src/delete.ts. You probably need to run `pnpm prebuild` and restart the site.For browser wallet flows, compose this pattern with useSignAndExecuteTransaction from Sui dApp Kit: build the delete transaction using walrus.executeDeleteBlobTransaction, sign it with the connected wallet, and execute it through the dApp Kit hook.
Extending the lifetime of blobs
You purchase blob storage for a set number of epochs. You can extend the storage period of a blob before it expires.
To extend a blob, call walrus.extendBlob with the blob object ID and the additional number of epochs to add. The Walrus SDK documentation covers the full API. For browser wallet flows, compose the extension transaction with useSignAndExecuteTransaction from Sui dApp Kit.
Data encryption
All blobs stored on Walrus are public. To restrict access to content, you must encrypt it before uploading and decrypt it after retrieval. OnlyFins demonstrates this pattern end-to-end using Seal.
Seal is a decentralized secrets management service that provides threshold encryption and onchain access control for Walrus data. It integrates directly with Sui's object model, letting you tie decryption access to ownership of specific Sui objects or other onchain conditions. In OnlyFins, a user receives a ViewerToken Sui object after paying for a post, and Seal checks ownership of that token before issuing decryption key shares.
The backend encryptImages.ts script handles encryption before upload. The frontend usePostDecryption hook handles decryption after reading. Encryption with Seal covers both in depth and builds on this same OnlyFins app.