Skip to content

User Buckets

In this tutorial, we'll walk through the key steps to building file hosting and sharing on IPFS into your application. To do it, we'll use Buckets and we'll use an example that allows users of your app to post photo galleries to IPFS, IPNS, and HTTP using Buckets.

Getting Started

There are a few resources you'll need before you start writing code.

  • A new Typescript web app. We recommend using Typescript, as Textile libraries are in a period rapid of development and type detection is valuable during upgrades.
  • You should already be familiar with how to create user identities and how to generate API keys.

Initialize Buckets

In your app, there are two items you will regularly use when building on Buckets. The first is the Buckets class object where you will initialize a API client for your user and call bucket methods. The second is the key of any bucket you want to interact with regularly, since you will need to tell the API which Bucket you are acting on.

So, to get started in our app, we are going to do three things at once.

  1. Create a new Bucket object.
  2. Create or fetch the existing bucket of interest by name.
  3. Get the key of the bucket.


For this tutorial, you will be using an API key generated as part of a User Group key. It is possible to use Account Keys together with these APIs, but they do not work in quite the same way, since only Account owners (or Org members) can use them. A User Group key will allow you to create buckets for each user of your app.

import { Buckets, Identity, KeyInfo } from '@textile/hub'

const setup = async (key: KeyInfo, identity: Identity) => {
  // Use the insecure key to set up the buckets client
  const buckets = await Buckets.withKeyInfo(key)
  // Authorize the user and your insecure keys with getToken
  await buckets.getToken(identity) 

  const result = await'io.textile.dropzone')
  if (!result.root) {
    throw new Error('Failed to open bucket')
  return {
      buckets: buckets, 
      bucketKey: result.root.key,

Create a photo index

If you are going to allow your users to upload images, or files, that may become more than a few, it can be helpful to track metadata in an index. In our final example, we resample the photos on the fly, storing multiple sizes for better display performance. We track all those files with a simple JSON index in the root of our bucket. It would be better to store that index right in the user's Thread! But we wanted to keep this tutorial basic.

import { Buckets, Identity } from '@textile/hub'

const initIndex = async (buckets: Buckets, bucketKey: string, identity: Identity) => {
  // Create a json model for the index
  const index = {
    author: identity.public.toString(),
    date: (new Date()).getTime(),
    paths: [],
  // Store the index in the Bucket (or in the Thread later)
  const buf = Buffer.from(JSON.stringify(index, null, 2))
  const path = `index.json`
  await buckets.pushPath(bucketKey, path, buf)

Now, you can update the paths each time you add new images to the bucket. In our example, we add 4 files for every image, full res, medium res, thumbnail, and metdata. We also update the paths with a link to the file's own metadata on each update. In this way, an app can load just the single list of metadata and decide what to display.

Create a public view

Buckets are cross-protocol objects, meaning you can use them in IPFS, IPNS or HTTP. If you want to create a public view of bucket over HTTP, you should add an index.html to the root. In our example, we add an index.html that knows how to parse and display files based on the ./index.json stored above, in the same bucket.

import { Buckets, Identity } from '@textile/hub'

const addIndexHTML = async (buckets: Buckets, bucketKey: string, html: string) => {
  // Store the index.html in the root of the bucket
  const buf = Buffer.from(html)
  const path = `index.html`
  await buckets.pushPath(bucketKey, path, buf)

Push files

You are now ready to start pushing your files to the bucket. You can push each binary file to a specific path in the bucket using pushPath.

import { Buckets, PushPathResult } from '@textile/hub'

const insertFile = (buckets: Buckets, bucketKey: string, file: File, path: string): Promise<PushPathResult> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onabort = () => reject('file reading was aborted')
    reader.onerror = () => reject('file reading has failed')
    reader.onload = () => {
      const binaryStr = reader.result
      // Finally, push the full file to the bucket
      buckets.pushPath(bucketKey, path, binaryStr).then((raw) => {

At this point, we also update our index.json with the new file.

Push encrypted buckets

If your app is providing private spaces for your users to organize their photos or files, you can also create encrypted buckets for them. The open and init methods on the Bucket class take an isPrivate option. So your bucket start method may look more like,

import { Buckets } from '@textile/hub'

const openEncrypted = async (buckets: Buckets) => {
  const isEncrypted = true
  const result = await'io.textile.encrypted', undefined, isEncrypted)
  if (!result.root) {
    throw new Error('Failed to open bucket')
  return {
      buckets: buckets, 
      bucketKey: result.root.key,

Sharing encrypted buckets

There is no way to convert encrypted Buckets to non-encrypted or vice-versa. However, it should be straight-forward to move files from an encrypted Bucket into a non-encrypted Bucket and back again.

Adding multiple readers or writers to Buckets is only currently available through orgs for developers, not app users. However, we will include this ability in future release. This is dependent on our work to implement more advanced Threads ACLs.

Be aware that creating encrypted Buckets still posts files to IPFS. Meaning the encrypted contents of Buckets are still publicly available, just encrypted so not possible to view without the encryption keys.

Example on GitHub

git clone
cd js-examples/bucket-photo-gallery