How to upload files to Notion via automation (Make, Zapier, n8n, Pipedream)

If you have used the Notion API before May 2025 and needed to upload files, you found out you couldn't upload files directly; you could only link to externally hosted files (e.g., Google Drive, Dropbox, or similar file storage service).

Now, the Notion API also supports direct (and indirect) file uploads. This means you can upload files to Notion properties or blocks via the API without needing an external hosting service. It can be useful for automated workflows that involve file generation and storage. For example, automatically generating and attaching invoices to your dedicated Notion database, or uploading contract files to Notion, and all the other possible use cases you can imagine. This post explains everything you need to know about the Notion File Upload API.

General principles#

There are two types of file uploads via the Notion API:

  1. Direct Uploads — file uploads to Notion-managed storage (multi-part/form data)
  2. Indirect Uploads — file uploads of externally hosted files into a Notion workspace (i.e., upload from a file url)

Uploaded files can be attached to file, image, pdf, audio, and video block types. You can also set a file_upload on a page’s cover or icon, or in files properties in pages under a database.

Downloading a file you’ve uploaded. Aside from making the file available in the Notion app, attaching a File Upload to a page also allows you to retrieve the page in the API to get a temporary URL to access the file. After the expiry_time shown in the response (generally 1 hour), the page (or block, or database) must be fetched again to generate a new URL.

Notion doesn’t provide any permanent download links for files you’ve uploaded.

— source: documentation

There are three steps (three API calls) to upload a file to Notion via the API:

  1. Create a file upload: This generates a file upload ID, which you will use in the following step to upload the file to Notion. Use the mode body parameter to define if it’s a single-part upload (file <20MB), multi-part (file >20MB), or external_url (you upload the file from an external URL). Include the other parameters based on your needs as per the documentation.

  2. Upload the file: Now that you have the file_upload_id, you will include it in the URL for the request to upload the file to Notion. For this request, use the Content-Type header with multipart/form-data as its value. Include the file parameter in the body of the request, and optionally part_number (for multi-part uploads — i.e., files > 20MB).

  3. Attach the file to a block/property: Use the API endpoint to update/append a block or create/update a database item and include the file data in your request. See details below.

    This step uses standard Notion API endpoints; there’s no special upload-specific API for attaching. Just pass a file object with a type of file_upload and include the id you received earlier.

    You can reuse the same file_upload ID across multiple pages or blocks. Upload once, attach many times.

    Files must be attached to a page or block within 1 hour of creation, or they’ll be automatically deleted.

    — source: documentation

Direct uploads#

Direct upload means uploading a file directly to Notion via multipart/form-data requests, not using an external URL. If a file is <20MB in size, you can upload it with one request. For larger files (>20MB), you will first split the file into smaller parts (Notion recommends 10MB each), and then upload it across multiple multipart/form-data requests.

For direct uploads, the three steps written in the previous section is all you need. Here is a more detailed breakdown of each step:

  1. Create a file upload. This generates a unique upload ID and URL, which you will use in the next request (step 2).

    Example cURL:

javascript
curl --request POST \
--url 'https://api.notion.com/v1/file_uploads' \
-H 'Authorization: Bearer ntn_****' \
-H 'Content-Type: application/json' \
-H 'Notion-Version: 2022-06-28' \
--data '{}'
  1. Upload the file. Use the upload ID or URL from step 1 to send the binary file to Notion. Make sure to include the Content-Type header as multipart/form-data. Include the file key in the form fields of the request.

    Example cURL:

javascript
curl --request POST \
  --url 'https://api.notion.com/v1/file_uploads/a3f9d3e2-1abc-42de-b904-badc0ffee000/send' \
  -H 'Authorization: Bearer ntn_****' \
  -H 'Notion-Version: 2022-06-28' \
  -H 'Content-Type: multipart/form-data' \
  -F "file=@path/to-file.gif"
  1. Attach the file to a Notion block. Do this within 1 hour from the file upload request, else the file upload will be automatically deleted.

    Once the file’s status is uploaded, you can attach it to Notion content using the file_upload ID. This ID can be passed anywhere a File object is accepted — including properties on database pages and content blocks like images or files.

    This step uses standard Notion API endpoints; there’s no special upload-specific API for attaching. Just pass a file object with a type of file_upload and include the id you received earlier.

    — source: documentation

Uploading large files (>20MB) in multiple parts#

When uploading files greater than 20MB in size, you need to split the file into multiple parts and send multiple requests to the Notion API. According to the Notion documentation, splitting large files into multiple parts of around 10MB each is the best approach. In any case, each file part must be between 5MB and 20MB in size, “with the exception of the final part (the one with the highest part number), which can be less than 5MB.” (source: documentation).

After splitting the file into multiple parts, the next step to upload a large file to Notion via the API is to create a file upload in multi_part mode.

Example cURL:

javascript
curl --request POST \\
  --url '<https://api.notion.com/v1/file_uploads>' \\
  --header 'Authorization: Bearer ntn_****' \\
  --header 'Content-Type: application/json' \\
  --header 'Notion-Version: 2022-06-28' \\
  --data '{"mode": "multi_part", "number_of_parts": 3, "filename": "photo.png", "content_type": "image/png"}'

Next, you send each file part, specifying which part_number you are sending

Example cURL:

javascript
curl --request POST \\
  --url '<https://api.notion.com/v1/file_uploads/a3f9d3e2-1abc-42de-b904-badc0ffee000/send>' \\
  --header 'Authorization: Bearer ntn_****' \\
  --header 'Notion-Version: 2022-06-28' \\
  --header 'Content-Type: multipart/form-data' \\
  -F "file=@image_to_upload_split_aa.png"
  -F "part_number=1"

Last, you complete the file upload using the dedicated endpoint.

Example cURL:

javascript
curl --request POST \\
  --url '<https://api.notion.com/v1/file_uploads/a3f9d3e2-1abc-42de-b904-badc0ffee000/complete>' \\
  --header 'Authorization: Bearer ntn_****' \\
  --header 'Content-Type: application/json' \\
  --header 'Notion-Version: 2022-06-28' \\
  --data '{}'

Indirect uploads (import external files into Notion)#

Finally, you may also import a file from a URL into Notion. For example, if you have a publicly accessible file in Dropbox/Google Drive, you can upload the file into Notion directly using the file link (web content link) via the API. Please note the URL needs to be publicly accessible. Here are the steps to do this:

  1. Create a file upload with the external url: use the Create File Upload API with a mode of external_url, a filename, and the external_url.

    Example cURL:

javascript
curl --request POST \\
  --url '<https://api.notion.com/v1/file_uploads>' \\
  --header 'Authorization: Bearer ntn_****' \\
  --header 'Content-Type: application/json' \\
  --header 'Notion-Version: 2022-06-28' \\
  --data '{"mode": "external_url", "external_url": "<https://example.com/temp.png>", "filename": "photo.png", "content_type": "image/png"}'
  1. Wait for the upload to complete: the file gets upload asynchronously. You can do one of two things to get the final uploaded file (source: documentation):

    1. Polling. Set up your integration to wait a sequence of intervals (e.g. 5, 15, 30, and 45 seconds) after creating the File Upload and poll the Retrieve File Upload API until the status is uploaded.

    2. 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).

      • Try again later or with a smaller file. You won’t be able to use this File Upload in Step 3.

  2. Attach the file to a block or database property: this is the same step as described in previous sections.

Resources#

Receive a weekly email with latest essays and exclusive insights on making a life you want. Join more than 500 subscribers.