SECURITY REVIEW
Not yet assessed
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
Integrate Tigris file storage, file operations, presigned URLs, and client uploads.
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.
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
How clearly the skill guides your agent, how complete its workflow is, and how you can check the outcome.
No quality assessment is available for this catalog entry yet.
Original instructions from the publisher’s 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
```bash
# 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
```
```bash
# .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
```
```typescript
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
```bash
npm install -g @tigrisdata/cli
```
Verify the installation:
```bash
tigris --version
```
`t3` is an alias for `tigris` — all commands work with either.
### Step 2: Authenticate
```bash
tigris login
```
Opens browser for OAuth. After login, verify with:
```bash
tigris whoami
```
For CI/CD or non-interactive environments:
```bash
tigris configure --access-key <key> --access-secret <secret>
```
### Step 3: Create Bucket
```bash
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
```bash
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:
```bash
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
```bash
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
```bash
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:
```typescript
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`:
```typescript
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:
```typescript
// 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
```typescript
put(path: string, body: string | ReadableStream | Blob | Buffer, options?: PutOptions)
```
```typescript
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
```typescript
get(path: string, format: "string" | "file" | "stream", options?: GetOptions)
```
```typescript
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
```typescript
remove(path: string, options?: RemoveOptions)
```
```typescript
import { remove } from "@tigrisdata/storage";
const result = await remove("notes/hello.txt");
if (result.error) {
console.error(result.error);
}
```
### list — List Objects
```typescript
list(options?: ListOptions)
```
```typescript
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
```typescript
head(path: string, options?: HeadOptions)
```
```typescript
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
```typescript
getPresignedUrl(path: string, options: GetPresignedUrlOptions)
```
```typescript
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
```typescript
// 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
```typescript
"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.
```bash
# 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` | Use the **tigris-python-sdk** skill — covers 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` |
| Laravel | Forge / Vapor | Dashboard → Environment or `vapor env:pull` |
---
## Related Skills
- **tigris-python-sdk** — Python access via boto3, Django uploads, snapshots, forks, Bundle API
- **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
- SDK: https://www.tigrisdata.com/docs/sdks/tigris/
- Client Uploads: https://www.tigrisdata.com/docs/sdks/tigris/client-uploads/
- Examples: https://www.tigrisdata.com/docs/sdks/tigris/examples/