skills/tigrisdata/skills/file-storage

file-storage

SKILL.md

Tigris File Storage

Store and serve files with Tigris Object Storage. Covers CLI setup (bucket, access keys, auth) and the @tigrisdata/storage SDK for application code.

Quick Start

# 1. Install CLI & authenticate
npm install -g @tigrisdata/cli
tigris login

# 2. Create bucket and access key
tigris buckets create my-app-uploads
tigris access-keys create "my-app-uploads-key"
# ⚠ Save the Secret Access Key — shown only once
tigris access-keys assign tid_xxx --bucket my-app-uploads --role Editor

# 3. Install SDK
npm install @tigrisdata/storage
# .env
TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_yyy
TIGRIS_STORAGE_ENDPOINT=https://t3.storage.dev
TIGRIS_STORAGE_BUCKET=my-app-uploads
import { put } from "@tigrisdata/storage";

// Files are private by default — only authenticated requests can access them
const result = await put("avatars/user-123.jpg", file);
if (result.error) throw result.error;
console.log(result.data?.url);

// Use access: "public" only when anonymous users need direct URL access
// const result = await put("avatars/user-123.jpg", file, { access: "public" });

See Getting Started with CLI below for detailed steps.


Getting Started with CLI

Step 1: Install CLI

npm install -g @tigrisdata/cli

Verify the installation:

tigris --version

t3 is an alias for tigris — all commands work with either.

Step 2: Authenticate

tigris login

Opens browser for OAuth. After login, verify with:

tigris whoami

For CI/CD or non-interactive environments:

tigris configure --access-key <key> --access-secret <secret>

Step 3: Create Bucket

tigris buckets create my-app-uploads

Key points:

  • Buckets are private by default. Use --public for publicly readable objects.
  • Buckets are global by default. Use --locations to pin to specific regions.
  • Type help after any command to see its options (e.g., tigris buckets create help).

Step 4: Create Access Key

tigris access-keys create "my-app-uploads-key"

This outputs an Access Key ID (tid_xxx) and Secret Access Key (tsec_yyy).

The Secret Access Key is only shown once. Copy it immediately. The Name field is only for human identification — it has no functional impact.

Step 5: Configure Environment

Create .env in your project root:

TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_yyy
TIGRIS_STORAGE_ENDPOINT=https://t3.storage.dev
TIGRIS_STORAGE_BUCKET=my-app-uploads

TIGRIS_STORAGE_BUCKET sets the default bucket for all SDK calls. Add .env to .gitignore — never commit credentials.

Step 6: Assign Access Key to Bucket

tigris access-keys assign tid_xxx --bucket my-app-uploads --role Editor

Roles:

Role Permissions Use when
Editor Read + write + delete objects App servers that upload/delete files
ReadOnly Read objects only Apps that only serve/download files

Now you have:

  • A bucket (my-app-uploads)
  • An access key (tid_xxx / tsec_yyy)
  • The key assigned to the bucket with Editor role
  • A .env file ready for the SDK

Step 7: Install SDK

npm install @tigrisdata/storage
# or
yarn add @tigrisdata/storage

Supports ES Modules and CommonJS.


SDK Reference

All methods return TigrisStorageResponse<T, E>. Always check error first:

const result = await put("file.txt", "hello");
if (result.error) {
  console.error(result.error);
  return;
}
console.log(result.data);

config — Override Default Configuration

Every method accepts an optional config parameter of type TigrisStorageConfig:

type TigrisStorageConfig = {
  bucket?: string;          // Override TIGRIS_STORAGE_BUCKET
  accessKeyId?: string;     // Override TIGRIS_STORAGE_ACCESS_KEY_ID
  secretAccessKey?: string; // Override TIGRIS_STORAGE_SECRET_ACCESS_KEY
  endpoint?: string;        // Override TIGRIS_STORAGE_ENDPOINT
};

Use config to target a different bucket or use different credentials per call:

// Upload to a different bucket
await put("report.pdf", data, { config: { bucket: "reports-archive" } });

// Use a separate read-only key for downloads
await get("file.txt", "string", { config: { accessKeyId: "tid_ro", secretAccessKey: "tsec_ro" } });

put — Upload

put(path: string, body: string | ReadableStream | Blob | Buffer, options?: PutOptions)
import { put } from "@tigrisdata/storage";

// Simple text upload
const result = await put("notes/hello.txt", "Hello, World!");

// Image with public access
const result = await put("avatars/user-123.jpg", file, {
  access: "public",
  contentType: "image/jpeg",
  addRandomSuffix: false,
});

// Large file with multipart and progress
const result = await put("videos/demo.mp4", fileStream, {
  multipart: true,
  onUploadProgress: ({ loaded, total, percentage }) => {
    console.log(`${loaded}/${total} bytes (${percentage}%)`);
  },
});

// Prevent accidental overwrite
const result = await put("config.json", data, {
  allowOverwrite: false,
});

Put options:

Option Values Default Purpose
access public, private private Object visibility
addRandomSuffix boolean false Append random suffix to avoid collisions on user-uploaded files with the same name
allowOverwrite boolean true Allow replacing existing file
contentType MIME string inferred Content type header
contentDisposition inline,attachment inline Browser display behavior
multipart boolean false Enable for large files
onUploadProgress callback {loaded, total, percentage}
config TigrisStorageConfig Override bucket/credentials (see config section above)

Response data: { url, path, size, contentType, contentDisposition, modified }

get — Download

get(path: string, format: "string" | "file" | "stream", options?: GetOptions)
import { get } from "@tigrisdata/storage";

// Read as string (text, JSON)
const result = await get("notes/hello.txt", "string");
console.log(result.data); // "Hello, World!"

// Serve as file (for API routes)
const result = await get("avatars/user-123.jpg", "file", {
  contentDisposition: "inline",
});

// Trigger browser download
const result = await get("reports/q4.pdf", "file", {
  contentDisposition: "attachment",
});

// Stream large files
const result = await get("videos/demo.mp4", "stream");

Get options:

Option Values Default Purpose
contentDisposition inline,attachment inline Display vs download
contentType MIME string from upload Override content type
encoding string utf-8 Text encoding
config TigrisStorageConfig Override bucket/credentials (see config section above)

remove — Delete

remove(path: string, options?: RemoveOptions)
import { remove } from "@tigrisdata/storage";

const result = await remove("notes/hello.txt");
if (result.error) {
  console.error(result.error);
}

list — List Objects

list(options?: ListOptions)
import { list } from "@tigrisdata/storage";

// List all objects
const result = await list();
console.log(result.data?.items);

// Filter by prefix
const result = await list({ prefix: "avatars/" });

// Paginate through all objects
const allFiles = [];
let page = await list({ limit: 100 });
allFiles.push(...(page.data?.items ?? []));

while (page.data?.hasMore) {
  page = await list({
    limit: 100,
    paginationToken: page.data.paginationToken,
  });
  allFiles.push(...(page.data?.items ?? []));
}

List options:

Option Purpose
prefix Filter keys starting with this string
delimiter Group keys (e.g., "/" for folders)
limit Max objects per page (default: 100)
paginationToken Continue from previous page
config TigrisStorageConfig — override bucket/credentials (see config section above)

Response data: { items, paginationToken, hasMore }

head — Object Metadata

head(path: string, options?: HeadOptions)
import { head } from "@tigrisdata/storage";

const result = await head("avatars/user-123.jpg");
if (!result.error) {
  console.log(result.data);
  // { path, size, contentType, contentDisposition, modified, url }
}

getPresignedUrl — Temporary URLs

getPresignedUrl(path: string, options: GetPresignedUrlOptions)
import { getPresignedUrl } from "@tigrisdata/storage";

// Temporary download link (1 hour)
const result = await getPresignedUrl("reports/q4.pdf", {
  operation: "get",
  expiresIn: 3600,
});
console.log(result.data?.url);

// Temporary upload link (10 minutes)
const result = await getPresignedUrl("uploads/photo.jpg", {
  operation: "put",
  expiresIn: 600,
});

Presigned URL options:

Option Values Default Purpose
operation get,put URL purpose
expiresIn seconds 3600 Expiration time
contentType MIME string Required for PUT
config TigrisStorageConfig Override bucket/credentials (see config section above)

Response data: { url, method, expiresIn }


Client-Side Uploads

Upload files directly from the browser to Tigris without routing bytes through your server. Uses presigned URLs under the hood.

Server — Handle Upload Requests

// app/api/upload/route.ts
import { NextRequest, NextResponse } from "next/server";
import { handleClientUpload } from "@tigrisdata/storage";

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { data, error } = await handleClientUpload(body);
    if (error) {
      return NextResponse.json({ error: error.message }, { status: 500 });
    }
    return NextResponse.json({ data });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to process upload request" },
      { status: 500 },
    );
  }
}

Client — Direct Upload

"use client";

import { upload } from "@tigrisdata/storage/client";
import { useState } from "react";

export default function FileUpload() {
  const [progress, setProgress] = useState(0);
  const [url, setUrl] = useState<string | null>(null);

  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    const result = await upload(file.name, file, {
      url: "/api/upload",
      access: "private",
      multipart: true,
      partSize: 10 * 1024 * 1024,
      onUploadProgress: ({ percentage }) => {
        setProgress(percentage);
      },
    });

    setUrl(result.url);
  };

  return (
    <>
      <input type="file" onChange={handleFileChange} />
      {progress > 0 && progress < 100 && <div>{progress}%</div>}
      {url && <div>Uploaded: {url}</div>}
    </>
  );
}

Client upload options:

Option Required Purpose
url Yes Backend endpoint for presigned URLs
access No public or private (default)
multipart No Enable for large files
partSize No Bytes per part (default: 5 MiB)
concurrency No Parallel part uploads (default: 4)
contentType No MIME type
onUploadProgress No {loaded, total, percentage}

React Component (Optional)

npm install @tigrisdata/react provides a drop-in <Uploader> component with file selection, progress, and error handling built in. See @tigrisdata/react docs for usage.


Critical Rules

Always: Check result.error before result.data | Upload files as private by default — only set access: "public" when anonymous users need direct URL access | Use handleClientUpload for browser uploads (don't route bytes through server) | Use multipart: true for files over 100MB | Paginate list() with hasMore + paginationToken | Delete old files when replacing (no auto-cleanup) | Set contentType explicitly when it matters

Never: Expose access keys to the client (use handleClientUpload + upload() from @tigrisdata/storage/client) | Skip error checking | Use generic paths like file.jpg (use avatars/${userId}.jpg or timestamps) | Forget to save the Secret Access Key on creation (shown only once)


Known Issues

Problem Cause & Fix
"Access denied" on upload Key not assigned to bucket. Run tigris access-keys assign tid_xxx --bucket <name> --role Editor
"Bucket not found" from SDK Wrong bucket name in .env. Verify with tigris buckets list
Secret Access Key lost Cannot recover. Create new: tigris access-keys create "new-key" and reassign
Files not publicly accessible Bucket is private by default. Use --public flag or access: "public" on put()
Upload hangs on large files Add multipart: true to put options for files over 100MB
List returns incomplete results Default limit is 100. Use hasMore + paginationToken to paginate
Client upload fails (CORS/500) Server route must use handleClientUpload from @tigrisdata/storage

CLI Quick Reference

t3 is an alias for tigris. Type help after any command for options.

# Auth
tigris login
tigris whoami

# Buckets
tigris buckets create <name> [--public] [--locations <region>]
tigris buckets list
tigris buckets delete <name>

# Access keys
tigris access-keys create "<name>"
tigris access-keys assign <tid_xxx> --bucket <name> --role Editor

# Objects
tigris cp <src> <dest> [-r]         # Upload/download/copy
tigris mv <src> <dest> [-rf]        # Move or rename
tigris rm <path> [-rf]              # Delete
tigris ls [bucket/prefix]           # List
tigris stat <path>                  # Metadata
tigris presign <path>               # Presigned URL
tigris touch <path>                 # Create empty object

Remote paths use t3:// prefix: t3://my-bucket/path/file.txt


Framework Integration Guides

For framework-specific upload/download patterns, read the resource file for your framework:

Framework SDK Resource
Next.js @tigrisdata/storage (native) Read ./resources/nextjs.md — Server Actions, API Routes, next/image, client uploads
Remix @tigrisdata/storage (native) Read ./resources/remix.md — action functions, loaders, client uploads
Express @tigrisdata/storage (native) Read ./resources/express.md — Multer, streaming uploads, client uploads
Rails aws-sdk-s3 (no native Ruby SDK yet) Read ./resources/rails.md — Active Storage, direct uploads, image variants
Django tigris-boto3-ext + django-storages Read ./resources/django.md — FileField, django-storages, presigned URLs
Laravel league/flysystem-aws-s3-v3 (no native PHP SDK yet) Read ./resources/laravel.md — Storage facade, Livewire uploads, presigned URLs

Deployment

Framework Platform Set env vars with
Next.js Vercel Dashboard → Settings → Environment Variables
Remix Fly.io fly secrets set TIGRIS_STORAGE_ACCESS_KEY_ID=... ...
Express Docker -e flags or .env in Compose
Rails Fly.io / Kamal fly secrets set or kamal env push
Django Fly.io fly secrets set
Laravel Forge / Vapor Dashboard → Environment or vapor env:pull

Related Skills

  • tigris-bucket-management — Advanced bucket options (regions, tiers, snapshots)
  • tigris-object-operations — Detailed SDK function reference
  • tigris-snapshots-forking — Point-in-time recovery and bucket forking

Official Documentation

Weekly Installs
9
GitHub Stars
2
First Seen
Mar 16, 2026
Installed on
opencode9
codex9
claude-code7
github-copilot6
cursor6
gemini-cli5