or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cookie-session.mdglobal-polyfills.mdindex.mdserver-runtime.mdsession-storage.mdstream-utilities.mdupload-handling.md

upload-handling.mddocs/

0

# Upload Handling

1

2

File upload processing for multipart form data with disk storage, size limits, and Node.js File interface implementation.

3

4

## Capabilities

5

6

### Create File Upload Handler

7

8

Creates an UploadHandler that saves uploaded files to disk with configurable options for security and performance.

9

10

```typescript { .api }

11

/**

12

* Creates an upload handler that saves files to disk

13

* @param options - Configuration options for file upload handling

14

* @returns UploadHandler function for processing uploaded files

15

*/

16

function unstable_createFileUploadHandler(

17

options?: FileUploadHandlerOptions

18

): UploadHandler;

19

20

interface FileUploadHandlerOptions {

21

/** Avoid file conflicts by appending a count on the end of the filename if it already exists on disk. Defaults to true */

22

avoidFileConflicts?: boolean;

23

/** The directory to write the upload */

24

directory?: string | FileUploadHandlerPathResolver;

25

/** The name of the file in the directory. Can be a relative path, the directory structure will be created if it does not exist */

26

file?: FileUploadHandlerPathResolver;

27

/** The maximum upload size allowed. If the size is exceeded an error will be thrown. Defaults to 3000000B (3MB) */

28

maxPartSize?: number;

29

/** Filter function to determine if a file should be processed */

30

filter?(args: FileUploadHandlerFilterArgs): boolean | Promise<boolean>;

31

}

32

33

type FileUploadHandlerPathResolver = (

34

args: FileUploadHandlerPathResolverArgs

35

) => string | undefined;

36

37

interface FileUploadHandlerFilterArgs {

38

filename: string;

39

contentType: string;

40

name: string;

41

}

42

43

interface FileUploadHandlerPathResolverArgs {

44

filename: string;

45

contentType: string;

46

name: string;

47

}

48

```

49

50

**Usage Examples:**

51

52

```typescript

53

import { unstable_createFileUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";

54

55

// Basic file upload handler

56

const uploadHandler = unstable_createFileUploadHandler({

57

directory: "/tmp/uploads"

58

});

59

60

// Advanced configuration

61

const uploadHandler = unstable_createFileUploadHandler({

62

directory: ({ name, filename, contentType }) => {

63

// Organize by content type

64

if (contentType.startsWith("image/")) {

65

return "/uploads/images";

66

}

67

return "/uploads/documents";

68

},

69

file: ({ filename }) => {

70

// Generate unique filename

71

const timestamp = Date.now();

72

const ext = filename ? path.extname(filename) : "";

73

return `upload_${timestamp}${ext}`;

74

},

75

maxPartSize: 5 * 1024 * 1024, // 5MB

76

filter: ({ name, filename, contentType }) => {

77

// Only allow specific file types

78

return contentType.startsWith("image/") || contentType === "application/pdf";

79

},

80

avoidFileConflicts: true

81

});

82

83

// Use in a Remix action

84

export async function action({ request }: ActionFunctionArgs) {

85

const formData = await unstable_parseMultipartFormData(

86

request,

87

uploadHandler

88

);

89

90

const file = formData.get("upload") as NodeOnDiskFile;

91

92

if (file) {

93

console.log("Uploaded file:", file.name);

94

console.log("File path:", file.getFilePath());

95

console.log("File size:", file.size);

96

}

97

98

return json({ success: true });

99

}

100

```

101

102

### Node On Disk File

103

104

File implementation for files stored on the Node.js filesystem, providing a complete File interface.

105

106

```typescript { .api }

107

/**

108

* File implementation for files stored on Node.js filesystem

109

*/

110

class NodeOnDiskFile implements Omit<File, "constructor"> {

111

/** The name of the file (basename) */

112

name: string;

113

/** Last modified timestamp (always 0 for new files) */

114

lastModified: number;

115

/** WebKit relative path (always empty string) */

116

webkitRelativePath: string;

117

/** File size in bytes (computed dynamically) */

118

get size(): number;

119

/** MIME type of the file */

120

type: string;

121

/** File prototype for proper type identification */

122

prototype: typeof File.prototype;

123

124

/**

125

* Creates a new NodeOnDiskFile instance

126

* @param filepath - Absolute path to the file on disk (private)

127

* @param type - MIME type of the file

128

* @param slicer - Optional slice configuration for partial file access

129

*/

130

constructor(

131

filepath: string,

132

type: string,

133

slicer?: { start: number; end: number }

134

);

135

136

/**

137

* Creates a new Blob containing a subset of the file's data

138

* @param start - Starting byte offset (optional)

139

* @param end - Ending byte offset (optional)

140

* @param type - MIME type for the new blob (optional)

141

* @returns New Blob with the specified slice

142

*/

143

slice(start?: number, end?: number, type?: string): Blob;

144

145

/**

146

* Reads the entire file into an ArrayBuffer

147

* @returns Promise resolving to file contents as ArrayBuffer

148

*/

149

arrayBuffer(): Promise<ArrayBuffer>;

150

151

/**

152

* Creates a ReadableStream for streaming file contents

153

* @returns ReadableStream for the file

154

*/

155

stream(): ReadableStream;

156

stream(): NodeJS.ReadableStream;

157

stream(): ReadableStream | NodeJS.ReadableStream;

158

159

/**

160

* Reads the entire file as text

161

* @returns Promise resolving to file contents as string

162

*/

163

text(): Promise<string>;

164

165

/**

166

* Removes the file from disk

167

* @returns Promise that resolves when file is deleted

168

*/

169

remove(): Promise<void>;

170

171

/**

172

* Gets the absolute file path on disk

173

* @returns Absolute path to the file

174

*/

175

getFilePath(): string;

176

177

/**

178

* Symbol.toStringTag for proper type identification

179

* @returns "File"

180

*/

181

get [Symbol.toStringTag](): string;

182

}

183

```

184

185

**File Handling Examples:**

186

187

```typescript

188

import { NodeOnDiskFile } from "@remix-run/node";

189

190

// Working with uploaded files

191

export async function action({ request }: ActionFunctionArgs) {

192

const formData = await unstable_parseMultipartFormData(request, uploadHandler);

193

const file = formData.get("document") as NodeOnDiskFile;

194

195

if (file) {

196

// Get file information

197

console.log("File name:", file.name);

198

console.log("File size:", file.size, "bytes");

199

console.log("MIME type:", file.type);

200

console.log("Path on disk:", file.getFilePath());

201

202

// Read file contents

203

const text = await file.text();

204

const buffer = await file.arrayBuffer();

205

206

// Stream file contents

207

const stream = file.stream();

208

209

// Create a slice of the file

210

const firstKB = file.slice(0, 1024);

211

212

// Clean up (remove from disk)

213

await file.remove();

214

}

215

216

return json({ processed: true });

217

}

218

```

219

220

**Security Features:**

221

222

- **File size limits**: Configurable maximum upload size with automatic cleanup on exceed

223

- **Content filtering**: Filter function to validate file types and properties

224

- **Conflict avoidance**: Automatic filename modification to prevent overwrites

225

- **Secure paths**: Path resolution prevents directory traversal attacks

226

- **Cleanup on error**: Automatic file removal if processing fails

227

228

**Performance Features:**

229

230

- **Streaming processing**: Files are processed as streams to handle large uploads

231

- **Lazy loading**: File contents are only read when accessed

232

- **Efficient slicing**: File slicing uses Node.js streams for memory efficiency

233

- **Directory creation**: Automatic creation of necessary directory structure

234

235

**Error Handling:**

236

237

- **MaxPartSizeExceededError**: Thrown when file exceeds size limit

238

- **Automatic cleanup**: Failed uploads are automatically removed from disk

239

- **Graceful degradation**: Returns undefined for filtered or invalid files