Skip to content
Blog / How to build a file manager with Appwrite Storage
5 min

How to build a file manager with Appwrite Storage

Learn how to create buckets, upload and download files, set permissions, and build a complete file manager UI using Appwrite Storage.

File management is one of those features that sounds simple until you're deep in the weeds: S3 bucket policies, pre-signed URLs, CORS headers, permission checks. Appwrite Storage is designed to cut through that complexity with a clean API and bucket-based organization that maps naturally to most app architectures.

This post walks through creating buckets, uploading and downloading files, listing files in a bucket, and setting permissions, then ties it all together with a simple file manager UI flow.

What Appwrite Storage gives you

Appwrite Storage handles file uploads, downloads, deletions, and listings through a REST API (and SDK wrappers for all major platforms). Files live in buckets, which are isolated containers with their own permission settings, size limits, and allowed MIME types.

Storage is intentionally separate from Appwrite Databases. Databases store structured data: user profiles, product records, settings. Storage stores files: images, PDFs, videos, rows. Both can be used together, and it's common to store a file ID in a database row to link them.

Creating a bucket

Before you can store anything, you need a bucket. You can create one in the Appwrite Console or via the Server SDK.

In the Console, go to Storage, click Create bucket, give it a name, and configure the settings: maximum file size, allowed file extensions, and whether files are encrypted at rest.

Via the Node.js Server SDK:

JavaScript
import { Client, Storage, ID } from 'node-appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<API_KEY>');

const storage = new Storage(client);

const bucket = await storage.createBucket(
  ID.unique(),       // bucketId
  'User Uploads',    // name
  ['any'],           // permissions (covered below)
  true,             // fileSecurity
  true,              // enabled
  10000000,          // maximumFileSize (10 MB)
  ['image/jpeg', 'image/png', 'application/pdf'] // allowedFileExtensions (MIME types or extensions, e.g. 'jpg')
);

The fileSecurity flag is important. When set to true, each file can have its own permissions in addition to bucket-level permissions. When false, only bucket-level permissions apply. For a multi-user file manager, you'll want fileSecurity: true.

Uploading files

From the browser, use the Client SDK:

JavaScript
import { Client, Storage, ID } from 'appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const storage = new Storage(client);

async function uploadFile(bucketId, file) {
  const result = await storage.createFile({
    bucketId,
    fileId: ID.unique(),
    file,
    // Permissions for this specific file
    permissions: [
      'read("user:<USER_ID>")',
      'update("user:<USER_ID>")',
      'delete("user:<USER_ID>")'
    ]
  });
  return result;
}

The file argument is a standard browser File object from an <input type="file"> element. The SDK handles chunked uploads automatically for large files, so you don't need to manage that yourself.

For a file manager UI, wire this up to a file input or drag-and-drop zone:

JavaScript
const fileInput = document.getElementById('file-input');

fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  const uploaded = await uploadFile('<BUCKET_ID>', file);
  console.log('Uploaded:', uploaded.$id);
  await refreshFileList();
});

Listing files

To display uploaded files, use listFiles:

JavaScript
async function listFiles(bucketId) {
  const files = await storage.listFiles({ bucketId });
  return files.files; // array of file objects
}

Each file object contains metadata: $id, name, mimeType, sizeOriginal, $createdAt, and $updatedAt. You can use this to render a table or grid view without downloading the actual file content.

JavaScript
async function renderFileList(bucketId) {
  const files = await listFiles(bucketId);
  const list = document.getElementById('file-list');

  list.innerHTML = files.map(file => `
    <div class="file-item" data-id="${file.$id}">
      <span>${file.name}</span>
      <span>${(file.sizeOriginal / 1024).toFixed(1)} KB</span>
      <span>${new Date(file.$createdAt).toLocaleDateString()}</span>
      <button onclick="downloadFile('${bucketId}', '${file.$id}', '${file.name}')">
        Download
      </button>
      <button onclick="deleteFile('${bucketId}', '${file.$id}')">
        Delete
      </button>
    </div>
  `).join('');
}

Downloading files

To download a file, get the file view URL and trigger a browser download:

JavaScript
async function downloadFile(bucketId, fileId, fileName) {
  const url = storage.getFileDownload({ bucketId, fileId });
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
}

getFileDownload returns a URL that forces a download. If you want to display images inline instead, use getFileView, which serves the file with appropriate content headers.

For image previews, Appwrite also offers built-in transformations. You can request a resized thumbnail without any third-party service:

JavaScript
const thumbnailUrl = storage.getFilePreview({
  bucketId,
  fileId,
  width: 200,
  height: 200,
  gravity: 'center',
  quality: 80
});

Build fast, scale faster

Backend infrastructure and web hosting built for developers who ship.

  • checkmark icon Start for free
  • checkmark icon Open source
  • checkmark icon Support for over 13 SDKs
  • checkmark icon Managed cloud solution

Setting permissions

Permissions in Appwrite follow a consistent pattern across all resources. For storage, you set permissions at two levels: the bucket and (when fileSecurity is enabled) the individual file.

Permission strings take the form action("role"). Common patterns:

JavaScript
// Only authenticated users can read
'read("users")'

// Only a specific user can read, update, and delete
'read("user:<USER_ID>")'
'update("user:<USER_ID>")'
'delete("user:<USER_ID>")'

// A team with a specific role
'read("team:<TEAM_ID>/editor")'
'update("team:<TEAM_ID>/editor")'

// Public read access (anyone, including guests)
'read("any")'

For a typical file manager where each user owns their files:

  • Set the bucket to allow authenticated users to create files: 'create("users")'
  • Set each file's permissions to the uploading user only: 'read("user:<USER_ID>")', 'update("user:<USER_ID>")', 'delete("user:<USER_ID>")'

This way, users can only see and manage their own files, even though all files live in the same bucket.

Deleting files

JavaScript
async function deleteFile(bucketId, fileId) {
  await storage.deleteFile({ bucketId, fileId });
  await renderFileList(bucketId);
}

Putting it together: a minimal file manager

Here's a sketch of the full flow:

  1. On page load, call listFiles and render the file list.
  2. A file input or drop zone calls uploadFile when the user selects a file, using Permission.write(Role.user(currentUser.$id)) for ownership.
  3. Each row in the list has download and delete buttons wired to getFileDownload and deleteFile.
  4. For image files, show a thumbnail using getFilePreview.

The result is a functional, permissioned file manager backed entirely by Appwrite, no custom file storage server required.

Start building with Appwrite Storage

Appwrite Storage handles the infrastructure so you can focus on building the experience. Buckets, permissions, image transformations, and chunked uploads are all included out of the box.

Start building with Appwrite today

Get started