AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

File Storage

skill-tigrisdata-skills-file-storage · by tigrisdata

Use when working with Tigris file storage - uploading, downloading, deleting, listing files, presigned URLs, client uploads, or setting up Tigris CLI and SDK. Covers Next.js, Remix, Express, Rails, and Laravel. For Python/Django, see the tigris-python-sdk skill.

No reviews yet
0 installs
39 views
0.0% view→install

Install

$ agentstack add skill-tigrisdata-skills-file-storage

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-tigrisdata-skills-file-storage)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of File Storage? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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  --access-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. 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(null);

  const handleFileChange = async (e: React.ChangeEvent) => {
    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 (
    <>
      
      {progress > 0 && progress {progress}%}
      {url && Uploaded: {url}}
    
  );
}

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 ` 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 --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  [--public] [--locations ]
tigris buckets list
tigris buckets delete 

# Access keys
tigris access-keys create ""
tigris access-keys assign  --bucket  --role Editor

# Objects
tigris cp   [-r]         # Upload/download/copy
tigris mv   [-rf]        # Move or rename
tigris rm  [-rf]              # Delete
tigris ls [bucket/prefix]           # List
tigris stat                   # Metadata
tigris presign                # Presigned URL
tigris touch                  # 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

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.