0

Upload File

by
Published today

Upload a file from Windmill object storage to Notion, optionally attaching it to a page as a file block.

Script notion Verified

The script

Submitted by hugo989 Bun
Verified 3 hours ago
1
import * as wmill from "windmill-client"
2
import { S3Object } from "windmill-client"
3

4
/**
5
 * Upload File
6
 * Upload a file from Windmill object storage to Notion. Files over 20 MiB are sent in 10 MiB parts. Pass `attach_to_page_id` to append it to a page as a file block; otherwise use the returned `id` within an hour (as a `file_upload` in a block, cover, icon or files property) or it expires. Free workspaces cap files at 5 MiB.
7
 */
8
export async function main(
9
  auth: RT.Notion,
10
  file: S3Object,
11
  filename: string,
12
  content_type: string | undefined,
13
  attach_to_page_id: string | undefined
14
) {
15
  const bytes = await wmill.loadS3File(file)
16
  if (!bytes) {
17
    throw new Error("Could not load the file from object storage")
18
  }
19
  const headers = {
20
    Authorization: `Bearer ${auth.token}`,
21
    Accept: "application/json",
22
    "Notion-Version": "2026-03-11",
23
  }
24
  const call = async (path: string, init: RequestInit) => {
25
    const response = await fetch(`https://api.notion.com/v1/${path}`, {
26
      ...init,
27
      headers: { ...headers, ...init.headers },
28
    })
29
    if (!response.ok) {
30
      throw new Error(`${response.status} ${await response.text()}`)
31
    }
32
    return await response.json()
33
  }
34

35
  const PART = 10 * 1024 * 1024
36
  const parts =
37
    bytes.length > 20 * 1024 * 1024 ? Math.ceil(bytes.length / PART) : 1
38
  const create: { [key: string]: any } = { filename }
39
  if (content_type !== undefined && content_type !== "") {
40
    create.content_type = content_type
41
  }
42
  if (parts > 1) {
43
    create.mode = "multi_part"
44
    create.number_of_parts = parts
45
  }
46
  const upload = await call("file_uploads", {
47
    method: "POST",
48
    headers: { "Content-Type": "application/json" },
49
    body: JSON.stringify(create),
50
  })
51

52
  let result = upload
53
  for (let i = 0; i < parts; i++) {
54
    const form = new FormData()
55
    const chunk = parts > 1 ? bytes.subarray(i * PART, (i + 1) * PART) : bytes
56
    form.append(
57
      "file",
58
      new Blob([chunk], { type: content_type || undefined }),
59
      filename
60
    )
61
    if (parts > 1) form.append("part_number", String(i + 1))
62
    result = await call(`file_uploads/${upload.id}/send`, {
63
      method: "POST",
64
      body: form,
65
    })
66
  }
67
  if (parts > 1) {
68
    result = await call(`file_uploads/${upload.id}/complete`, {
69
      method: "POST",
70
    })
71
  }
72

73
  if (attach_to_page_id !== undefined && attach_to_page_id !== "") {
74
    await call(`blocks/${attach_to_page_id}/children`, {
75
      method: "PATCH",
76
      headers: { "Content-Type": "application/json" },
77
      body: JSON.stringify({
78
        children: [
79
          {
80
            type: "file",
81
            file: { type: "file_upload", file_upload: { id: upload.id } },
82
          },
83
        ],
84
      }),
85
    })
86
  }
87

88
  return result
89
}
90