Upload File to Notion
Upload local files to Notion using Notion's three-step file upload API
Source Code
import fs from 'fs';
import path from 'path';
import mime from 'mime-types';
// Constants
const MULTIPART_THRESHOLD = 20 * 1024 * 1024; // 20 MB
const PART_SIZE = 10 * 1024 * 1024; // 10 MB per part
/**
* Upload a local file to Notion using Notion's three-step upload process
* @param {string} filePath - Local path to the file to upload
* @param {string} notionToken - Notion API token
* @returns {Promise<Object>} Upload result with file URL and metadata
*/
async function uploadFileToNotion(filePath, notionToken) {
// Read the file
const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(filePath);
const fileSize = fileBuffer.length;
// Detect content type
const contentType = mime.lookup(filePath) || 'application/octet-stream';
// Automatically determine upload mode based on file size
const useMultipart = fileSize > MULTIPART_THRESHOLD;
// Step 1: Create a file upload
const createUploadBody = {
name: fileName,
content_type: contentType
};
if (useMultipart) {
// Calculate number of parts for multipart upload
const numberOfParts = Math.ceil(fileSize / PART_SIZE);
createUploadBody.mode = 'multi_part';
createUploadBody.number_of_parts = numberOfParts;
console.log(`[DEBUG] File size ${fileSize} bytes exceeds ${MULTIPART_THRESHOLD} bytes, using multipart upload with ${numberOfParts} parts`);
} else {
// Use single part upload for smaller files
createUploadBody.mode = 'single_part';
console.log(`[DEBUG] File size ${fileSize} bytes, using single-part upload`);
}
const createUploadResponse = await fetch('https://api.notion.com/v1/file_uploads', {
method: 'POST',
headers: {
'Authorization': `Bearer ${notionToken}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(createUploadBody)
});
const createUploadData = await createUploadResponse.json();
if (!createUploadResponse.ok) {
throw new Error(`Failed to create file upload: ${createUploadData.message || createUploadData.error}`);
}
const { id: fileUploadId } = createUploadData;
// Step 2: Send the file contents
if (useMultipart) {
// Send file in multiple parts
const numberOfParts = Math.ceil(fileSize / PART_SIZE);
for (let partNumber = 1; partNumber <= numberOfParts; partNumber++) {
const start = (partNumber - 1) * PART_SIZE;
const end = Math.min(start + PART_SIZE, fileSize);
const partBuffer = fileBuffer.subarray(start, end);
const formData = new FormData();
formData.append('file', new Blob([partBuffer], { type: contentType }), fileName);
formData.append('part_number', partNumber.toString());
console.log(`[DEBUG] Uploading part ${partNumber}/${numberOfParts} (${partBuffer.length} bytes)`);
const sendUploadResponse = await fetch(
`https://api.notion.com/v1/file_uploads/${fileUploadId}/send`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${notionToken}`,
'Notion-Version': '2022-06-28'
},
body: formData
}
);
if (!sendUploadResponse.ok) {
const sendUploadError = await sendUploadResponse.text();
throw new Error(`Failed to send file part ${partNumber}: ${sendUploadError}`);
}
}
} else {
// Send file in single part
const formData = new FormData();
formData.append('file', new Blob([fileBuffer], { type: contentType }), fileName);
const sendUploadResponse = await fetch(
`https://api.notion.com/v1/file_uploads/${fileUploadId}/send`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${notionToken}`,
'Notion-Version': '2022-06-28'
},
body: formData
}
);
if (!sendUploadResponse.ok) {
const sendUploadError = await sendUploadResponse.text();
throw new Error(`Failed to send file contents: ${sendUploadError}`);
}
}
// Step 3: Complete the file upload
const completeUploadResponse = await fetch(
`https://api.notion.com/v1/file_uploads/${fileUploadId}/complete`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${notionToken}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
}
}
);
const completeUploadData = await completeUploadResponse.json();
if (!completeUploadResponse.ok) {
throw new Error(`Failed to complete file upload: ${completeUploadData.message || completeUploadData.error}`);
}
const fileUrl = completeUploadData.file.url;
const expiryTime = completeUploadData.file.expiry_time;
console.log(`[DEBUG] Successfully uploaded file to Notion: ${fileName}`);
console.log(`[DEBUG] File Upload ID: ${fileUploadId}`);
console.log(`[DEBUG] File URL: ${fileUrl}`);
console.log(`[DEBUG] Expires at: ${expiryTime}`);
return {
success: true,
fileUploadId: fileUploadId,
fileName: fileName,
url: fileUrl,
expiryTime: expiryTime,
contentType: contentType,
fileSize: fileSize
};
}
export { uploadFileToNotion };
/**
* Run this script directly from the command line:
* bun upload_file.js <filePath> <notionToken>
*
* The script automatically determines whether to use single-part or multi-part upload
* based on file size (files >20MB use multi-part upload automatically).
*
* Returns the Notion-hosted file URL which you can then use in API calls to add to pages/databases.
*
* Examples:
* # Upload local file
* bun upload_file.js ./document.pdf token123
*
* # Upload image
* bun upload_file.js ./photo.jpg token123
*/
if (import.meta.main) {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: bun upload_file.js <filePath> <notionToken>');
console.error('');
console.error('The script automatically uses multi-part upload for files >20MB.');
console.error('Returns a Notion-hosted URL that you can use in API calls.');
console.error('');
console.error('Examples:');
console.error(' # Upload local file');
console.error(' bun upload_file.js ./doc.pdf token123');
console.error('');
console.error(' # Upload image');
console.error(' bun upload_file.js ./photo.jpg token123');
process.exit(1);
}
const filePath = args[0];
const notionToken = args[1];
uploadFileToNotion(filePath, notionToken)
.then(result => {
console.log('\nā Upload successful!');
console.log(`File Upload ID: ${result.fileUploadId}`);
console.log(`File Name: ${result.fileName}`);
console.log(`File Size: ${result.fileSize} bytes`);
console.log(`Content Type: ${result.contentType}`);
console.log(`URL: ${result.url}`);
console.log(`Expires: ${result.expiryTime}`);
console.log('\nUse the File Upload ID in Notion API calls to add the file to pages or databases.');
})
.catch(error => {
console.error('Upload failed:', error.message);
process.exit(1);
});
}