0
# File and URL Operations
1
2
Secure file access through signed URLs and file URL management for media and document handling.
3
4
## Capabilities
5
6
### Get Signed File URLs
7
8
Generates signed URLs for secure access to files hosted on Notion's servers. Required for accessing files in private workspaces or when authentication is needed.
9
10
```typescript { .api }
11
/**
12
* Generates signed URLs for secure file access
13
* @param urls - Array of URL requests with permission records
14
* @param kyOptions - HTTP client options
15
* @returns Promise resolving to signed URLs response
16
*/
17
async getSignedFileUrls(
18
urls: SignedUrlRequest[],
19
kyOptions?: KyOptions
20
): Promise<SignedUrlResponse>;
21
22
interface SignedUrlRequest {
23
/** Permission record for the file */
24
permissionRecord: PermissionRecord;
25
/** Original file URL to sign */
26
url: string;
27
}
28
29
interface PermissionRecord {
30
/** Table type - typically 'block' for file blocks */
31
table: string;
32
/** Block ID containing the file */
33
id: string;
34
}
35
36
interface SignedUrlResponse {
37
/** Array of signed URLs corresponding to input requests */
38
signedUrls: string[];
39
}
40
```
41
42
**Usage Examples:**
43
44
```typescript
45
import { NotionAPI } from "notion-client";
46
47
const api = new NotionAPI({ authToken: "your-token" });
48
49
// Get a page with file blocks
50
const page = await api.getPage("page-with-files");
51
52
// Find file blocks and prepare URL requests
53
const urlRequests: SignedUrlRequest[] = [];
54
55
Object.entries(page.block).forEach(([blockId, record]) => {
56
const block = record.value;
57
58
if (block.type === "image" || block.type === "file" || block.type === "pdf") {
59
const fileUrl = block.properties?.source?.[0]?.[0];
60
61
if (fileUrl && fileUrl.includes("secure.notion-static.com")) {
62
urlRequests.push({
63
permissionRecord: {
64
table: "block",
65
id: blockId
66
},
67
url: fileUrl
68
});
69
}
70
}
71
});
72
73
// Get signed URLs
74
const signedResponse = await api.getSignedFileUrls(urlRequests);
75
76
// Map signed URLs back to blocks
77
signedResponse.signedUrls.forEach((signedUrl, index) => {
78
const originalRequest = urlRequests[index];
79
console.log(`Block ${originalRequest.permissionRecord.id}: ${signedUrl}`);
80
});
81
```
82
83
### Add Signed URLs
84
85
Automatically adds signed URLs to a record map for all file blocks that require them. This is typically called internally by `getPage()` when `signFileUrls` is true.
86
87
```typescript { .api }
88
/**
89
* Adds signed URLs to record map for file blocks
90
* @param options - Configuration for URL signing
91
* @returns Promise that resolves when URLs are added
92
*/
93
async addSignedUrls(options: AddSignedUrlsOptions): Promise<void>;
94
95
interface AddSignedUrlsOptions {
96
/** The record map to modify */
97
recordMap: ExtendedRecordMap;
98
/** Optional specific block IDs to process */
99
contentBlockIds?: string[];
100
/** HTTP client options */
101
kyOptions?: KyOptions;
102
}
103
104
interface ExtendedRecordMap {
105
block: Record<string, BlockRecord>;
106
collection: Record<string, CollectionRecord>;
107
collection_view: Record<string, CollectionViewRecord>;
108
notion_user: Record<string, UserRecord>;
109
collection_query: Record<string, any>;
110
/** Signed URLs mapped by block ID */
111
signed_urls: Record<string, string>;
112
}
113
```
114
115
**Usage Examples:**
116
117
```typescript
118
import { NotionAPI } from "notion-client";
119
120
const api = new NotionAPI({ authToken: "your-token" });
121
122
// Get page without automatic URL signing
123
const page = await api.getPage("page-id", { signFileUrls: false });
124
125
// Manually add signed URLs
126
await api.addSignedUrls({ recordMap: page });
127
128
// Now access signed URLs
129
Object.entries(page.signed_urls).forEach(([blockId, signedUrl]) => {
130
console.log(`Signed URL for block ${blockId}: ${signedUrl}`);
131
});
132
133
// Add signed URLs for specific blocks only
134
const specificBlockIds = ["block-1", "block-2"];
135
await api.addSignedUrls({
136
recordMap: page,
137
contentBlockIds: specificBlockIds
138
});
139
```
140
141
## File Type Support
142
143
Notion Client supports signing URLs for various file types:
144
145
**Supported File Block Types:**
146
147
```typescript
148
// File types that support signed URLs
149
const supportedFileTypes = [
150
"image", // Image files (JPG, PNG, GIF, etc.)
151
"file", // Generic file attachments
152
"pdf", // PDF documents
153
"video", // Video files
154
"audio", // Audio files
155
"page" // Pages with cover images
156
];
157
```
158
159
**Usage Examples:**
160
161
```typescript
162
const page = await api.getPage("page-id");
163
164
// Process different file types
165
Object.entries(page.block).forEach(([blockId, record]) => {
166
const block = record.value;
167
const signedUrl = page.signed_urls[blockId];
168
169
switch (block.type) {
170
case "image":
171
console.log("Image file:", signedUrl);
172
console.log("Alt text:", block.properties?.caption);
173
break;
174
175
case "file":
176
console.log("File attachment:", signedUrl);
177
console.log("Filename:", block.properties?.title);
178
break;
179
180
case "pdf":
181
console.log("PDF document:", signedUrl);
182
break;
183
184
case "video":
185
console.log("Video file:", signedUrl);
186
break;
187
188
case "audio":
189
console.log("Audio file:", signedUrl);
190
break;
191
192
case "page":
193
if (block.format?.page_cover) {
194
console.log("Page cover:", signedUrl);
195
}
196
break;
197
}
198
});
199
```
200
201
## URL Security and Expiration
202
203
Signed URLs have security considerations and expiration times:
204
205
**Usage Examples:**
206
207
```typescript
208
// Signed URLs are temporary - cache appropriately
209
const urlCache = new Map<string, { url: string; timestamp: number }>();
210
211
async function getCachedSignedUrl(blockId: string, originalUrl: string): Promise<string> {
212
const cached = urlCache.get(blockId);
213
const now = Date.now();
214
215
// URLs typically expire after several hours
216
if (cached && (now - cached.timestamp) < 6 * 60 * 60 * 1000) {
217
return cached.url;
218
}
219
220
// Generate new signed URL
221
const response = await api.getSignedFileUrls([{
222
permissionRecord: { table: "block", id: blockId },
223
url: originalUrl
224
}]);
225
226
const signedUrl = response.signedUrls[0];
227
urlCache.set(blockId, { url: signedUrl, timestamp: now });
228
229
return signedUrl;
230
}
231
```
232
233
## File URL Patterns
234
235
Different URL patterns indicate different file storage systems:
236
237
**Usage Examples:**
238
239
```typescript
240
function analyzeFileUrl(url: string): string {
241
if (url.includes("secure.notion-static.com")) {
242
return "secure-static"; // Requires signing
243
} else if (url.includes("prod-files-secure")) {
244
return "prod-secure"; // Requires signing
245
} else if (url.includes("attachment:")) {
246
return "attachment"; // Requires signing
247
} else if (url.startsWith("http")) {
248
return "external"; // External URL, no signing needed
249
} else {
250
return "unknown";
251
}
252
}
253
254
// Process files based on URL type
255
Object.entries(page.block).forEach(([blockId, record]) => {
256
const block = record.value;
257
const fileUrl = block.properties?.source?.[0]?.[0];
258
259
if (fileUrl) {
260
const urlType = analyzeFileUrl(fileUrl);
261
console.log(`Block ${blockId} (${block.type}): ${urlType}`);
262
263
if (urlType === "external") {
264
console.log("External file, no signing needed:", fileUrl);
265
} else if (page.signed_urls[blockId]) {
266
console.log("Signed URL available:", page.signed_urls[blockId]);
267
} else {
268
console.log("Signing required but not available");
269
}
270
}
271
});
272
```
273
274
## Error Handling
275
276
Handle various errors that can occur with file operations:
277
278
**Usage Examples:**
279
280
```typescript
281
try {
282
const signedResponse = await api.getSignedFileUrls(urlRequests);
283
284
// Check if all URLs were signed successfully
285
if (signedResponse.signedUrls.length !== urlRequests.length) {
286
console.warn("Some URLs could not be signed");
287
}
288
289
// Check for null/empty signed URLs
290
signedResponse.signedUrls.forEach((url, index) => {
291
if (!url) {
292
const originalRequest = urlRequests[index];
293
console.warn(`Failed to sign URL for block ${originalRequest.permissionRecord.id}`);
294
}
295
});
296
297
} catch (error) {
298
if (error.message.includes("permission")) {
299
console.error("Insufficient permissions to access files");
300
} else if (error.message.includes("not found")) {
301
console.error("File or block not found");
302
} else {
303
console.error("File signing failed:", error.message);
304
}
305
}
306
307
// Handle addSignedUrls errors
308
try {
309
await api.addSignedUrls({ recordMap: page });
310
} catch (error) {
311
console.warn("Some files could not be signed:", error.message);
312
// Page is still usable, just some files may not be accessible
313
}
314
```
315
316
## Types
317
318
```typescript { .api }
319
interface BlockRecord {
320
role: string;
321
value: Block;
322
}
323
324
interface Block {
325
id: string;
326
type: string;
327
properties?: Record<string, any>;
328
format?: Record<string, any>;
329
content?: string[];
330
created_time: number;
331
last_edited_time: number;
332
parent_id: string;
333
parent_table: string;
334
}
335
```