The File Upload object tracks the lifecycle of a file uploaded to Notion in the API.
Sending and attaching files
To upload a file, start by creating a File Upload. Its initial status
is pending
.
curl --request POST \
--url 'https://api.notion.com/v1/file_uploads' \
-H 'Authorization: Bearer ntn_****' \
-H 'Content-Type: application/json' \
-H 'Notion-Version: <<latestNotionVersion>>' \
--data '{}'
Then, send the File Upload by ID with the raw binary contents of the file supplied under the file
attribute. Unlike other Notion APIs, which accept JSON parameters, this endpoint accepts a content type of form data (multipart/form-data
). The FileUpload's status
changes from pending
to uploaded
once the upload is complete.
curl --request POST \
--url 'https://api.notion.com/v1/file_uploads/:file_upload_id/send' \
--header 'Authorization: Bearer ntn_****' \
--header 'Content-Type: multipart/form-data' \
--header 'Notion-Version: <<latestNotionVersion>>' \
-F "file=@path/to-file.png"
Once uploaded, the file upload ID lets your integration attach the file under File objects in the parameters of many Notion APIs (pages, databases, blocks):
- Create a page: for pages in databases, file properties can have
files
attached by providing afile
with atype
offile_upload
. The content of blocks underchildren
can also contain file objects that have a type offile_upload
and a"file_upload": {"id": "FILE_UPLOAD_ID"}
attribute.- A
file_upload
can also be attached as a page'scover
oricon
.
- A
- Update page properties:
files
properties; similar to Create a page. - Append block children: a
file_upload
object with anid
in it can be added to the content of the following block types:file
,image
,pdf
,audio
, andvideo
. - Update a block: existing blocks with a
type
offile
(or the others listed above under "Append block children") can be switched to or from file upload type blocks; for example, an image block with an external URL file can be updated to reference a file upload ID.
Example using Append block children:
curl --request PATCH \
--url 'https://api.notion.com/v1/blocks/$PAGE_OR_BLOCK_ID/children' \
-H "Authorization: Bearer ntn_*****" \
-H 'Content-Type: application/json' \
-H 'Notion-Version: <<latestNotionVersion>>' \
--data '{"children": [{"type": "file", "file": {"type": "file_upload", "file_upload": {"id": "$FILE_UPLOAD_ID"}}}]}'
File uploads must be uploaded and attached with one of the above APIs within one hour to avoid being automatically expired by Notion's systems. After attaching a file once, the expiry_time
property is cleared out and your integration can continue to use the same File Upload ID on further blocks, pages, or databases without needing to re-upload the original file.
File types and sizes
The following file extensions and content types are supported:
File Type | Extensions | MIME type |
---|---|---|
Audio | .aac , .mid , .midi , .mp3 , .ogg , .wav , .wma | audio/aac, audio/midi, audio/mpeg, audio/ogg, audio/wav, audio/x-ms-wma |
Image | .gif , .heic , .ico , .jpeg , .jpg , .png , .svg , .tif , .tiff , .webp | image/gif, image/heic, image/vnd.microsoft.icon, image/jpeg, image/png, image/svg+xml, image/tiff, image/webp |
Document | .json , .pdf , .txt | application/json, application/pdf, text/plain |
Video | .amv , .asf , .avi , .f4v , .flv , .gifv , .m4v , .mkv , .mov , .mp4 , .mpeg , .qt , .wmv | video/x-amv, video/x-ms-asf, video/x-msvideo, video/x-f4v, video/x-flv, video/mp4, video/mpeg, video/webm, video/quicktime, video/x-ms-wmv |
Uploaded files are limited to 5MB each in free workspaces, and 5GB each in paid workspaces. These are the same maximum file size limits that apply to files uploaded in the Notion app.
Sending larger files in multiple parts
To send files larger than 20MB, split them up into segments of 5-20 MB each. On Linux systems, one tool to do this is the split
command. In other toolchains, there are libraries such as split-file
for TypeScript to generate file parts.
# Split `largefile.txt` into 10MB chunks, named as follows:
# split_part_aa, split_part_ab, etc.
split -b 10M ./largefile.txt split_part
import * as splitFile from "split-file";
const filename = "movie.MOV";
const inputFile = `${__dirname}/${filename}`;
// Returns an array of file paths in the current
// directory with a format of:
// [
// "movie.MOV.sf-part1",
// "movie.MOV.sf-part2",
// ...
// ]
const outputFilenames = await splitFile.splitFileBySize(
inputFile,
1024 * 1024 * 10, // 10 MB
);
Convention for sizes of file parts
When sending parts of a file to the Notion API, each file must be ≥ 5MB and ≤ 20 (binary) megabytes in size, with the exception of the final part (the one with the highest part number), which can be less than 5MB. The
split
command respects this convention, but the tools in your tech stack might vary.To stay within the range, we recommend using a part size of 10MB.
Pass a mode
of "multi_part"
to the Create a file upload API, along with the number_of_parts
, and a filename
with a valid extension or a separate MIME content_type
parameter that can be used to detect an extension.
curl --request POST \
--url 'https://api.notion.com/v1/file_uploads' \
-H 'Authorization: Bearer ntn_****' \
-H 'Content-Type: application/json' \
-H 'Notion-Version: <<latestNotionVersion>>' \
--data '{
"mode": "multi_part",
"number_of_parts": 5,
"filename": "image.png"
}'
Send each file part by using the Send File Upload API using the File Upload ID or the upload_url
in the response of the Create a file upload step. Alongside the file
, the form data in your request must include a field part_number
that identifies which part you’re sending.
Your system can send file parts in parallel (up to standard Notion API rate limits). Parts can be uploaded in any order, as long as the entire sequence from {1, …, number_of_parts
} is successfully sent before calling the Complete a file upload API.
After completing the File Upload, its status becomes uploaded
and it can be attached to blocks and other objects the same way as file uploads created with a mode
of single_part
(the default setting).
Error handling
The Send API validates the total file size against the workspace's limit at the time of uploading each part. However, because parts can be sent at the same time, the Complete step re-validates the combined file size and can also return an HTTP 400 with a code of
validation_error
.We recommend checking the file's size before creating the File Upload when possible. Otherwise, make sure your integration can handle excessive file size errors returned from both the Send and Complete APIs.
To manually test your integration, command-line tools like
head
,dd
, andsplit
can help generate file contents of a certain size and split them into 10 MB parts.
Importing external files
To initiate the process of transferring a temporarily-hosted public file into your Notion workspace, use the Create a file upload with a mode
of "external_url"
, a filename
, and the external_url
itself:
curl --request POST \
--url 'https://api.notion.com/v1/file_uploads' \
-H 'Authorization: Bearer ntn_****' \
-H 'Content-Type: application/json' \
-H 'Notion-Version: <<latestNotionVersion>>' \
--data '{
"mode": "external_url",
"external_url": "https://example.com/image.png",
"filename": "image.png"
}'
At this step, Notion will return a validation_error
(HTTP 400) if any of the following are true:
- The URL is not SSL-enabled, or not publicly accessible.
- The URL doesn’t expose the
Content-Type
header for Notion to verify as part of a quickHEAD
HTTPS request. - The
Content-Length
header (size) of the file at the external URL exceeds your workspace’s per-file size limit. - You don’t provide a valid filename and a supported MIME content type or extension.
At this point, Notion begins processing the file import asynchronously. To wait for the upload to finish, your integration can do one of the following:
- Polling. Set up your integration to wait a sequence of intervals (e.g. 5, 15, 30, and 45 seconds, or an exponential backoff sequence) after creating the File Upload and poll the Retrieve a file upload until the
status
changes frompending
touploaded
(orfailed
). - Listen to webhooks. Notion will send one of the following types of integration webhook events:
file_upload.complete
- The import is complete, and your integration can proceed to using the FileUpload ID in Step 3.
file_upload.upload_failed
- The import failed. This is typically due to:
- File size is too large for your workspace (per-file limit exceeded).
- The external service temporarily hosting the file you’re importing is experiencing an outage, timing out, or requires authentication or additional headers at the time Notion’s systems retrieve your file.
- The file storage service Notion uses is experiencing an outage (rare).
- Check the
data[file_import_result]
object for error codes and messages to help troubleshoot. - Try again later or with a smaller file. You won’t be able to attach the failed File Upload to any blocks.
- The import failed. This is typically due to:
- For both success and failure, the
entity
of the webhook payload will contain atype
of"file_upload"
and anid
containing the ID of the FileUpload from Step 1.

Screenshot of webhook settings in the Notion creator profile integration settings page.
The outcome of the file import is recorded on the File Upload object. If the import fails, the status changes to failed
. If it succeeds, the status changes to uploaded
.
For example, in response to a file_upload.upload_failed
webhook, your system can read the data.file_import_result.error
from the webhook response, or use the Retrieve a file upload API and check the file_import_result.error
to debug the import failure:
// GET /v1/file_uploads/:file_upload_id
// --- RETURNS -->
{
"object": "file_upload",
// ...
"status": "**failed**",
"file_import_result": {
"type": "error",
"error": {
"type": "validation_error",
"code": "file_upload_invalid_size",
"message": "The file size is not within the allowed limit of 20MB. Please try again with a new file upload.",
"parameter": null,
"status_code": null
},
}
}
The file_import_result
object contains details on the success
or error
. In this example, the problem is a file size validation issue that wasn’t caught during Step 1—potentially because the external host did not provide a Content-Length
header for Notion to validate with a HEAD
request.
A file upload with a status of failed
cannot be reused, and a new one must be created.
Object properties
The response of File Upload APIs like Retrieve a file upload contains FileUpload
objects with the following fields:
Field | Type | Description |
---|---|---|
object | "file_upload" | |
id | UUID | ID of the FileUpload. |
created_time | String | ISO 8601 timestamp when the FileUpload was created. |
last_edited_time | String | ISO 8601 timestamp when the FileUpload was last modified. |
expiry_time | String | Nullable. ISO 8601 timestamp when the FileUpload will expire, if the API integration that created it doesn't complete the upload and attach to at least one block or other object in a workspace. |
status | One of: - "pending" - "uploaded" - "expired" - "failed" | Enum status of the file upload.pending status means awaiting upload or completion of an upload.uploaded status means file contents have been sent. If the expiry_time is null , that means the file upload has already been attached to a block or other object.expired and failed file uploads can no longer be used. failed is only used for FileUploads with mode=external_url when the import was unsuccessful. |
filename | String | Nullable. Name of the file, provided during the Create a file upload step, or, for single_part uploads, can be determined from the provided filename in the form data passed to the Send a file upload step.A file extension is automatically added based on the content_type if the filename doesn't already have one. |
content_type | String | Nullable. The MIME content type of the uploaded file. Must be provided explicitly or inferred from a filename that includes an extension.For single_part uploads, the content type can remain null until the Send a file upload step and inferred from the file parameter's content type. |
content_length | Integer | Nullable. The total size of the file, in bytes. For pending multi_part uploads, this field is a running total based on the file segments uploaded so far and recalculated at the end during the Complete a file upload step. |
upload_url | String | Field only included for pending file uploads.This is the URL to use for sending file contents. |
complete_url | String | Field only included for pending file uploads created with a mode of multi_part .This is the URL to use to complete a multi-part file upload. |
file_import_result | String | Field only included for a failed or uploaded file upload created with a mode of external_url .Provides details on the success or failure of importing a file into Notion using an external URL. |