How to Upload Files with Netlify Functions | Limits, Large Media, and Workarounds
When implementing file uploads with Netlify Functions (AWS Lambda-based serverless functions), request bodies have a size limit. Many developers encounter issues without knowing about this 6MB default limit. This article explains Netlify Functions' body size limit, configuration in <code>netlify.toml</code>, parsing methods for <code>multipart/form-data</code>, and alternative approaches for handling files beyond the limit.
Netlify Functions body size limit
Netlify Functions is based on AWS Lambda and has a fixed upper limit on request body size.
| Types of Functions | Body size limit | Changeable or not | Remarks |
|---|---|---|---|
| Netlify Functions (synchronous) | 6 MB | Not possible | AWS Lambda Limitations |
| Netlify Functions(Background) | 6 MB | Not possible | Processing time is maximum 15 minutes |
| Netlify Edge Functions | With Restrictions | Not possible | Deno-based Runtime |
Sending requests exceeding 6MB to Netlify Functions results in HTTP 413 or function timeout. While slightly larger than Vercel (4.5MB), large files like images or videos still cannot be received directly.
Configuration in netlify.toml
In <code>netlify.toml</code>, you can configure Functions runtime, timeout, memory, redirects, headers, and more. Body size itself cannot be changed, but understanding related settings is important.
# netlify.toml
[build]
command = "npm run build"
publish = ".next"
functions = "netlify/functions" # Functions のディレクトリ
# Node.js バンドラーの設定
[functions]
node_bundler = "esbuild"
# 特定の Function の設定
[functions."upload"]
included_files = ["uploads/**"]
# リダイレクト設定(Next.js など SPA との組み合わせ)
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
# ヘッダーの設定(CORS など)
[[headers]]
for = "/.netlify/functions/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers = "Content-Type, Authorization"
# 環境変数(本番用。機密情報は Netlify UI で設定すること)
[context.production.environment]
NODE_ENV = "production"
[context.deploy-preview.environment]
NODE_ENV = "development"
Parsing implementation of multipart/form-data
In Netlify Functions, request bodies may be Base64-encoded by default. We explain how to parse multipart data using <code>busboy</code> or <code>formidable</code>.
// netlify/functions/upload.js(CommonJS)
const busboy = require('busboy');
exports.handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
return new Promise((resolve, reject) => {
const contentType = event.headers['content-type'] || event.headers['Content-Type'];
const bb = busboy({ headers: { 'content-type': contentType } });
const files = [];
const fields = {};
bb.on('file', (name, file, info) => {
const { filename, encoding, mimeType } = info;
const chunks = [];
file.on('data', (data) => chunks.push(data));
file.on('end', () => {
const buffer = Buffer.concat(chunks);
// ファイルサイズの検証(6MB 上限の手前で確認)
const MAX_SIZE = 5 * 1024 * 1024; // 5MB(余裕を持たせる)
if (buffer.length > MAX_SIZE) {
resolve({
statusCode: 413,
body: JSON.stringify({ error: 'ファイルが大きすぎます(上限5MB)。' }),
});
return;
}
files.push({ name, filename, mimeType, buffer, size: buffer.length });
});
});
bb.on('field', (name, value) => {
fields[name] = value;
});
bb.on('finish', () => {
if (files.length === 0) {
resolve({
statusCode: 400,
body: JSON.stringify({ error: 'ファイルが見つかりません。' }),
});
return;
}
const file = files[0];
// ここでファイルを S3 などに保存する処理を行う
resolve({
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
filename: file.filename,
size: file.size,
mimeType: file.mimeType,
fields,
}),
});
});
bb.on('error', (err) => {
resolve({
statusCode: 500,
body: JSON.stringify({ error: err.message }),
});
});
// Netlify Functions ではボディが Base64 エンコードされる場合がある
const body = event.isBase64Encoded
? Buffer.from(event.body, 'base64')
: event.body;
bb.end(body);
});
};
// netlify/functions/upload-s3.js(S3 に転送する例)
const busboy = require('busboy');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
return new Promise((resolve) => {
const bb = busboy({
headers: { 'content-type': event.headers['content-type'] },
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB で切り捨て
});
bb.on('file', async (fieldName, stream, { filename, mimeType }) => {
const chunks = [];
let truncated = false;
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('limit', () => { truncated = true; });
stream.on('end', async () => {
if (truncated) {
return resolve({
statusCode: 413,
body: JSON.stringify({ error: 'ファイルが5MBを超えています。' }),
});
}
const buffer = Buffer.concat(chunks);
const key = `uploads/${Date.now()}-${filename}`;
try {
await s3.send(new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET,
Key: key,
Body: buffer,
ContentType: mimeType,
}));
resolve({
statusCode: 200,
body: JSON.stringify({
success: true,
key,
url: `https://${process.env.AWS_S3_BUCKET}.s3.amazonaws.com/${key}`,
}),
});
} catch (err) {
resolve({ statusCode: 500, body: JSON.stringify({ error: err.message }) });
}
});
});
const body = event.isBase64Encoded
? Buffer.from(event.body, 'base64')
: event.body;
bb.end(body);
});
};
Netlify Large Media (deprecated) and alternatives
Netlify Large Media was a service for managing large files based on Git LFS, but new signups are now restricted and migration to alternative solutions is recommended.
# Netlify Large Media(非推奨・新規利用不可)
# .lfsconfig
[lfs]
url = https://large-media.netlify.com/<repo-id>
The recommended alternative configuration is as follows.
// Presigned URL 経由で S3 に直接アップロードする Netlify Function
// netlify/functions/get-upload-url.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const s3 = new S3Client({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
const { filename, contentType, size } = JSON.parse(event.body);
// サーバー側でのバリデーション
const MAX_SIZE = 100 * 1024 * 1024; // 100MB
if (size > MAX_SIZE) {
return { statusCode: 413, body: JSON.stringify({ error: 'ファイルが大きすぎます。' }) };
}
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
if (!allowedTypes.includes(contentType)) {
return { statusCode: 415, body: JSON.stringify({ error: '許可されていない形式です。' }) };
}
const key = `uploads/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET,
Key: key,
ContentType: contentType,
ContentLength: size,
});
// 署名付き URL を生成(このリクエストはファイル本体を含まないため制限外)
const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); // 15分
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ presignedUrl, key }),
};
};
// クライアント側:Netlify Function から Presigned URL を取得して S3 に直接アップロード
async function uploadFile(file) {
// 1. Netlify Function から Presigned URL を取得
const res = await fetch('/.netlify/functions/get-upload-url', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
size: file.size,
}),
});
const { presignedUrl, key } = await res.json();
// 2. S3 に直接 PUT(Netlify Function を経由しないため容量制限なし)
const uploadRes = await fetch(presignedUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file,
});
if (!uploadRes.ok) throw new Error('アップロードに失敗しました');
return key;
}
Test files for this article (free)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG Test Image (1MB)</a> — For verifying upload behavior via Netlify Functions
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF Test File (1MB)</a> — for testing file uploads within 6MB limit
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP test file (1MB)</a> — For S3 direct upload testing via Presigned URL
Related articles
- → <a href="/ja/blog/vercel-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Vercel File Upload Configuration and Limits | Vercel Blob, API Restrictions, Workarounds</a>
- → <a href="/ja/blog/render-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">How to Configure File Upload on Render with PHP/Node.js | Disk, Environment Variables, S3 Integration</a>
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Summary of File Upload Limits for AWS S3 and CloudFront</a>