> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BufferedFile Class

> File info access and saveToDisk() method

The BufferedFile class provides information about uploaded files and saves files using the `saveToDisk()` method.

## BufferedFile Overview

<CardGroup cols={2}>
  <Card title="File Info" icon="circle-info">
    filename, mimetype, size extname, md5
  </Card>

  <Card title="saveToDisk()" icon="floppy-disk">
    Save to storage Auto URL generation
  </Card>

  <Card title="buffer" icon="memory">
    Buffer access `.buffer` getter
  </Card>

  <Card title="URL Properties" icon="link">
    url, signedUrl Auto-set after saving
  </Card>
</CardGroup>

## Basic Properties

### File Information

```typescript theme={null}
import type { BufferedFile } from "sonamu";

class FileModel extends BaseModelClass {
  @upload()
  async upload(): Promise<any> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Access file info
    console.log({
      filename: file.filename, // Original filename: "photo.jpg"
      mimetype: file.mimetype, // MIME type: "image/jpeg"
      size: file.size, // File size (bytes): 524288
      extname: file.extname, // Extension: "jpg" (without dot)
    });

    return {
      filename: file.filename,
      mimetype: file.mimetype,
      size: file.size,
      extname: file.extname,
    };
  }
}
```

**Property List**:

* `filename: string` - Original filename
* `mimetype: string` - MIME type
* `size: number` - File size (bytes)
* `extname: string | false` - Extension (without dot)
* `url: string | undefined` - Public URL after saving
* `signedUrl: string | undefined` - Signed URL after saving

## saveToDisk() Method

### Basic Usage

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async upload(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Save file (using default disk)
    const url = await file.saveToDisk("fs", `uploads/${Date.now()}-${file.filename}`);

    // Automatically saved to url property
    console.log(file.url); // Public URL
    console.log(file.signedUrl); // Signed URL

    return { url };
  }
}
```

### Save to Specific Disk

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async uploadToS3(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Save to S3 disk
    const url = await file.saveToDisk("s3", `uploads/${Date.now()}-${file.filename}`);

    return { url };
  }

  @upload()
  async uploadPublic(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Save to public disk
    const url = await file.saveToDisk("public", `public/${Date.now()}-${file.filename}`);

    return { url };
  }
}
```

## buffer Getter

### buffer Property

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async processImage(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const image = bufferedFiles?.[0];
    if (!image) throw new Error("Image is required");

    // Access Buffer directly
    const buffer = image.buffer;

    console.log("Buffer size:", buffer.length);

    // Image processing using Buffer
    const sharp = require("sharp");
    const resized = await sharp(buffer).resize(800, 600).toBuffer();

    // Save processed image
    // (saveToDisk saves original file,
    // use Storage Manager directly for processed Buffer)
    const disk = Sonamu.storage.use();
    const key = `images/${Date.now()}.jpg`;

    await disk.put(key, new Uint8Array(resized), {
      contentType: "image/jpeg",
    });

    const url = await disk.getUrl(key);

    return { url };
  }
}
```

<Info>`.buffer` is a getter that returns the pre-loaded Buffer from multipart parsing.</Info>

## MD5 Hash

### md5() Method

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async uploadWithHash(): Promise<{
    url: string;
    md5: string;
  }> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Calculate MD5 hash
    const md5Hash = await file.md5();
    console.log("MD5:", md5Hash); // "abc123def456..."

    // Check for duplicate files
    const rdb = this.getPuri("r");
    const existing = await rdb.table("files").where("md5_hash", md5Hash).first();

    if (existing) {
      // Same file already exists
      return {
        url: existing.url,
        md5: md5Hash,
      };
    }

    // Save new file
    const url = await file.saveToDisk("fs", `uploads/${md5Hash}.${file.extname}`);

    // Save to DB
    const wdb = this.getPuri("w");
    await wdb.table("files").insert({
      filename: file.filename,
      mime_type: file.mimetype,
      size: file.size,
      md5_hash: md5Hash,
      url,
    });

    return { url, md5: md5Hash };
  }
}
```

## File Validation

### Size Validation

```typescript theme={null}
class FileValidator {
  static validateSize(file: UploadedFile, maxSize: number): void {
    if (file.size > maxSize) {
      throw new Error(`File too large: ${file.size} bytes (max ${maxSize} bytes)`);
    }
  }
}

// Usage
class FileModel extends BaseModelClass {
  @upload()
  async upload(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Size validation (10MB)
    FileValidator.validateSize(file, 10 * 1024 * 1024);

    const url = await file.saveToDisk("fs", `uploads/${Date.now()}.${file.extname}`);

    return { url };
  }
}
```

### MIME Type Validation

```typescript theme={null}
class FileValidator {
  static validateMimeType(file: UploadedFile, allowedTypes: string[]): void {
    if (!allowedTypes.includes(file.mimetype)) {
      throw new Error(
        `Invalid file type: ${file.mimetype}. ` + `Allowed: ${allowedTypes.join(", ")}`,
      );
    }
  }
}

// Usage
class ImageModel extends BaseModelClass {
  @upload()
  async uploadImage(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const image = bufferedFiles?.[0];
    if (!image) throw new Error("Image is required");

    // MIME type validation
    FileValidator.validateMimeType(image, ["image/jpeg", "image/png", "image/gif", "image/webp"]);

    const url = await image.saveToDisk("fs", `images/${Date.now()}.${image.extname}`);

    return { url };
  }
}
```

### Extension Validation

```typescript theme={null}
class FileValidator {
  static validateExtension(file: UploadedFile, allowedExtensions: string[]): void {
    const ext = file.extname;

    if (!ext || !allowedExtensions.includes(ext.toLowerCase())) {
      throw new Error(
        `Invalid file extension: ${ext}. ` + `Allowed: ${allowedExtensions.join(", ")}`,
      );
    }
  }
}

// Usage
class DocumentModel extends BaseModelClass {
  @upload()
  async uploadDocument(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const document = bufferedFiles?.[0];
    if (!document) throw new Error("Document is required");

    // Extension validation
    FileValidator.validateExtension(document, ["pdf", "doc", "docx", "txt"]);

    const url = await document.saveToDisk("fs", `documents/${Date.now()}.${document.extname}`);

    return { url };
  }
}
```

### Integrated Validation Class

```typescript theme={null}
class FileValidator {
  static validateImage(file: UploadedFile): void {
    // Check size (5MB)
    if (file.size > 5 * 1024 * 1024) {
      throw new Error("Image too large (max 5MB)");
    }

    // Check MIME type
    const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
    if (!allowedTypes.includes(file.mimetype)) {
      throw new Error(`Invalid image type: ${file.mimetype}`);
    }

    // Check extension
    const allowedExtensions = ["jpg", "jpeg", "png", "gif", "webp"];
    if (!file.extname || !allowedExtensions.includes(file.extname.toLowerCase())) {
      throw new Error(`Invalid image extension: ${file.extname}`);
    }
  }

  static validateDocument(file: UploadedFile): void {
    // Check size (20MB)
    if (file.size > 20 * 1024 * 1024) {
      throw new Error("Document too large (max 20MB)");
    }

    // Check MIME type
    const allowedTypes = [
      "application/pdf",
      "application/msword",
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      "text/plain",
    ];

    if (!allowedTypes.includes(file.mimetype)) {
      throw new Error(`Invalid document type: ${file.mimetype}`);
    }
  }

  static validateVideo(file: UploadedFile): void {
    // Check size (100MB)
    if (file.size > 100 * 1024 * 1024) {
      throw new Error("Video too large (max 100MB)");
    }

    // Check MIME type
    const allowedTypes = ["video/mp4", "video/mpeg", "video/quicktime", "video/x-msvideo"];

    if (!allowedTypes.includes(file.mimetype)) {
      throw new Error(`Invalid video type: ${file.mimetype}`);
    }
  }
}

// Usage
class MediaModel extends BaseModelClass {
  @upload()
  async uploadImage(): Promise<{ url: string }> {
    const { bufferedFiles } = Sonamu.getContext();
    const image = bufferedFiles?.[0];
    if (!image) throw new Error("Image is required");

    FileValidator.validateImage(image);

    const url = await image.saveToDisk("fs", `images/${Date.now()}.${image.extname}`);

    return { url };
  }
}
```

## Practical Examples

### Profile Image Upload

```typescript theme={null}
class UserModel extends BaseModelClass {
  @upload()
  async uploadProfileImage(): Promise<{
    imageId: number;
    url: string;
    thumbnailUrl: string;
  }> {
    const context = Sonamu.getContext();

    if (!context.user) {
      throw new Error("Authentication required");
    }

    const { bufferedFiles } = context;
    const image = bufferedFiles?.[0];
    if (!image) throw new Error("Image is required");

    // Validation
    FileValidator.validateImage(image);

    // Original image Buffer
    const buffer = image.buffer;

    // Create thumbnail
    const sharp = require("sharp");
    const thumbnailBuffer = await sharp(buffer)
      .resize(200, 200, { fit: "cover" })
      .jpeg({ quality: 80 })
      .toBuffer();

    // Save original
    const originalKey = `profiles/${context.user.id}/original-${Date.now()}.${image.extname}`;
    const originalUrl = await image.saveToDisk("fs", originalKey);

    // Save thumbnail (using Storage Manager directly)
    const disk = Sonamu.storage.use();
    const thumbnailKey = `profiles/${context.user.id}/thumb-${Date.now()}.jpg`;
    await disk.put(thumbnailKey, new Uint8Array(thumbnailBuffer), {
      contentType: "image/jpeg",
    });
    const thumbnailUrl = await disk.getUrl(thumbnailKey);

    // Save to DB
    const wdb = this.getPuri("w");
    const [record] = await wdb
      .table("profile_images")
      .insert({
        user_id: context.user.id,
        original_key: originalKey,
        thumbnail_key: thumbnailKey,
        original_url: originalUrl,
        thumbnail_url: thumbnailUrl,
        mime_type: image.mimetype,
        size: image.size,
      })
      .returning("id");

    return {
      imageId: record.id,
      url: originalUrl,
      thumbnailUrl,
    };
  }
}
```

### Batch Processing Multiple Files

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async uploadMultiple(params: { category?: string }): Promise<{
    uploadedFiles: Array<{
      fileId: number;
      filename: string;
      url: string;
      md5: string;
    }>;
  }> {
    const { bufferedFiles } = Sonamu.getContext();
    const { category } = params;

    if (!bufferedFiles || bufferedFiles.length === 0) {
      throw new Error("At least one file is required");
    }

    if (bufferedFiles.length > 10) {
      throw new Error("Maximum 10 files allowed");
    }

    const uploadedFiles = [];
    const wdb = this.getPuri("w");

    for (const file of bufferedFiles) {
      // Validation
      if (file.size > 20 * 1024 * 1024) {
        throw new Error(`File ${file.filename} too large (max 20MB)`);
      }

      // MD5 hash
      const md5Hash = await file.md5();

      // Duplicate check
      const rdb = this.getPuri("r");
      const existing = await rdb.table("files").where("md5_hash", md5Hash).first();

      if (existing) {
        // File already exists
        uploadedFiles.push({
          fileId: existing.id,
          filename: existing.filename,
          url: existing.url,
          md5: md5Hash,
        });
        continue;
      }

      // Save new file
      const key = `uploads/${category}/${Date.now()}.${file.extname}`;
      const url = await file.saveToDisk("fs", key);

      // Save to DB
      const [record] = await wdb
        .table("files")
        .insert({
          key,
          filename: file.filename,
          mime_type: file.mimetype,
          size: file.size,
          md5_hash: md5Hash,
          url,
          category,
        })
        .returning("id");

      uploadedFiles.push({
        fileId: record.id,
        filename: file.filename,
        url,
        md5: md5Hash,
      });
    }

    return { uploadedFiles };
  }
}
```

## Accessing Original MultipartFile

### raw Property

```typescript theme={null}
class FileModel extends BaseModelClass {
  @upload()
  async uploadAdvanced(): Promise<any> {
    const { bufferedFiles } = Sonamu.getContext();
    const file = bufferedFiles?.[0];
    if (!file) throw new Error("File is required");

    // Access original Fastify MultipartFile
    const rawFile = file.raw;

    console.log({
      encoding: rawFile.encoding,
      fieldname: rawFile.fieldname,
      // ...
    });

    const url = await file.saveToDisk("fs", `uploads/${Date.now()}.${file.extname}`);

    return { url };
  }
}
```

## Cautions

<Warning>
  **Cautions when using UploadedFile**: 1. Validation is required before calling `saveToDisk()` 2.
  `.buffer` gives direct access to the pre-loaded Buffer 3. `url`, `signedUrl` are only available
  after saving 4. Consider streaming for large files 5. Verify Storage configuration
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="File Upload Setup" icon="gear" href="/en/api-development/file-upload/setup">
    @upload decorator setup
  </Card>

  <Card title="Saving Files" icon="floppy-disk" href="/en/api-development/file-upload/saving-files">
    Using Storage Manager
  </Card>

  <Card title="URL Generation" icon="link" href="/en/api-development/file-upload/url-generation">
    Generating URLs
  </Card>

  <Card title="@api Decorator" icon="at" href="/en/api-development/creating-apis/api-decorator">
    API basic usage
  </Card>
</CardGroup>
