CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-notion-client

Robust TypeScript client for the unofficial Notion API.

94

1.06x
Overview
Eval results
Files

files-urls.mddocs/

File and URL Operations

Secure file access through signed URLs and file URL management for media and document handling.

Capabilities

Get Signed File URLs

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.

/**
 * Generates signed URLs for secure file access
 * @param urls - Array of URL requests with permission records
 * @param kyOptions - HTTP client options
 * @returns Promise resolving to signed URLs response
 */
async getSignedFileUrls(
  urls: SignedUrlRequest[],
  kyOptions?: KyOptions
): Promise<SignedUrlResponse>;

interface SignedUrlRequest {
  /** Permission record for the file */
  permissionRecord: PermissionRecord;
  /** Original file URL to sign */
  url: string;
}

interface PermissionRecord {
  /** Table type - typically 'block' for file blocks */
  table: string;
  /** Block ID containing the file */
  id: string;
}

interface SignedUrlResponse {
  /** Array of signed URLs corresponding to input requests */
  signedUrls: string[];
}

Usage Examples:

import { NotionAPI } from "notion-client";

const api = new NotionAPI({ authToken: "your-token" });

// Get a page with file blocks
const page = await api.getPage("page-with-files");

// Find file blocks and prepare URL requests
const urlRequests: SignedUrlRequest[] = [];

Object.entries(page.block).forEach(([blockId, record]) => {
  const block = record.value;
  
  if (block.type === "image" || block.type === "file" || block.type === "pdf") {
    const fileUrl = block.properties?.source?.[0]?.[0];
    
    if (fileUrl && fileUrl.includes("secure.notion-static.com")) {
      urlRequests.push({
        permissionRecord: {
          table: "block",
          id: blockId
        },
        url: fileUrl
      });
    }
  }
});

// Get signed URLs
const signedResponse = await api.getSignedFileUrls(urlRequests);

// Map signed URLs back to blocks
signedResponse.signedUrls.forEach((signedUrl, index) => {
  const originalRequest = urlRequests[index];
  console.log(`Block ${originalRequest.permissionRecord.id}: ${signedUrl}`);
});

Add Signed URLs

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.

/**
 * Adds signed URLs to record map for file blocks
 * @param options - Configuration for URL signing
 * @returns Promise that resolves when URLs are added
 */
async addSignedUrls(options: AddSignedUrlsOptions): Promise<void>;

interface AddSignedUrlsOptions {
  /** The record map to modify */
  recordMap: ExtendedRecordMap;
  /** Optional specific block IDs to process */
  contentBlockIds?: string[];
  /** HTTP client options */
  kyOptions?: KyOptions;
}

interface ExtendedRecordMap {
  block: Record<string, BlockRecord>;
  collection: Record<string, CollectionRecord>;
  collection_view: Record<string, CollectionViewRecord>;
  notion_user: Record<string, UserRecord>;
  collection_query: Record<string, any>;
  /** Signed URLs mapped by block ID */
  signed_urls: Record<string, string>;
}

Usage Examples:

import { NotionAPI } from "notion-client";

const api = new NotionAPI({ authToken: "your-token" });

// Get page without automatic URL signing
const page = await api.getPage("page-id", { signFileUrls: false });

// Manually add signed URLs
await api.addSignedUrls({ recordMap: page });

// Now access signed URLs
Object.entries(page.signed_urls).forEach(([blockId, signedUrl]) => {
  console.log(`Signed URL for block ${blockId}: ${signedUrl}`);
});

// Add signed URLs for specific blocks only
const specificBlockIds = ["block-1", "block-2"];
await api.addSignedUrls({
  recordMap: page,
  contentBlockIds: specificBlockIds
});

File Type Support

Notion Client supports signing URLs for various file types:

Supported File Block Types:

// File types that support signed URLs
const supportedFileTypes = [
  "image",    // Image files (JPG, PNG, GIF, etc.)
  "file",     // Generic file attachments
  "pdf",      // PDF documents
  "video",    // Video files
  "audio",    // Audio files
  "page"      // Pages with cover images
];

Usage Examples:

const page = await api.getPage("page-id");

// Process different file types
Object.entries(page.block).forEach(([blockId, record]) => {
  const block = record.value;
  const signedUrl = page.signed_urls[blockId];
  
  switch (block.type) {
    case "image":
      console.log("Image file:", signedUrl);
      console.log("Alt text:", block.properties?.caption);
      break;
      
    case "file":
      console.log("File attachment:", signedUrl);
      console.log("Filename:", block.properties?.title);
      break;
      
    case "pdf":
      console.log("PDF document:", signedUrl);
      break;
      
    case "video":
      console.log("Video file:", signedUrl);
      break;
      
    case "audio":
      console.log("Audio file:", signedUrl);
      break;
      
    case "page":
      if (block.format?.page_cover) {
        console.log("Page cover:", signedUrl);
      }
      break;
  }
});

URL Security and Expiration

Signed URLs have security considerations and expiration times:

Usage Examples:

// Signed URLs are temporary - cache appropriately
const urlCache = new Map<string, { url: string; timestamp: number }>();

async function getCachedSignedUrl(blockId: string, originalUrl: string): Promise<string> {
  const cached = urlCache.get(blockId);
  const now = Date.now();
  
  // URLs typically expire after several hours
  if (cached && (now - cached.timestamp) < 6 * 60 * 60 * 1000) {
    return cached.url;
  }
  
  // Generate new signed URL
  const response = await api.getSignedFileUrls([{
    permissionRecord: { table: "block", id: blockId },
    url: originalUrl
  }]);
  
  const signedUrl = response.signedUrls[0];
  urlCache.set(blockId, { url: signedUrl, timestamp: now });
  
  return signedUrl;
}

File URL Patterns

Different URL patterns indicate different file storage systems:

Usage Examples:

function analyzeFileUrl(url: string): string {
  if (url.includes("secure.notion-static.com")) {
    return "secure-static"; // Requires signing
  } else if (url.includes("prod-files-secure")) {
    return "prod-secure"; // Requires signing
  } else if (url.includes("attachment:")) {
    return "attachment"; // Requires signing
  } else if (url.startsWith("http")) {
    return "external"; // External URL, no signing needed
  } else {
    return "unknown";
  }
}

// Process files based on URL type
Object.entries(page.block).forEach(([blockId, record]) => {
  const block = record.value;
  const fileUrl = block.properties?.source?.[0]?.[0];
  
  if (fileUrl) {
    const urlType = analyzeFileUrl(fileUrl);
    console.log(`Block ${blockId} (${block.type}): ${urlType}`);
    
    if (urlType === "external") {
      console.log("External file, no signing needed:", fileUrl);
    } else if (page.signed_urls[blockId]) {
      console.log("Signed URL available:", page.signed_urls[blockId]);
    } else {
      console.log("Signing required but not available");
    }
  }
});

Error Handling

Handle various errors that can occur with file operations:

Usage Examples:

try {
  const signedResponse = await api.getSignedFileUrls(urlRequests);
  
  // Check if all URLs were signed successfully
  if (signedResponse.signedUrls.length !== urlRequests.length) {
    console.warn("Some URLs could not be signed");
  }
  
  // Check for null/empty signed URLs
  signedResponse.signedUrls.forEach((url, index) => {
    if (!url) {
      const originalRequest = urlRequests[index];
      console.warn(`Failed to sign URL for block ${originalRequest.permissionRecord.id}`);
    }
  });
  
} catch (error) {
  if (error.message.includes("permission")) {
    console.error("Insufficient permissions to access files");
  } else if (error.message.includes("not found")) {
    console.error("File or block not found");  
  } else {
    console.error("File signing failed:", error.message);
  }
}

// Handle addSignedUrls errors
try {
  await api.addSignedUrls({ recordMap: page });
} catch (error) {
  console.warn("Some files could not be signed:", error.message);
  // Page is still usable, just some files may not be accessible
}

Types

interface BlockRecord {
  role: string;
  value: Block;
}

interface Block {
  id: string;
  type: string;
  properties?: Record<string, any>;
  format?: Record<string, any>;
  content?: string[];
  created_time: number;
  last_edited_time: number;
  parent_id: string;
  parent_table: string;
}

Install with Tessl CLI

npx tessl i tessl/npm-notion-client

docs

blocks-users.md

collections.md

core-api.md

files-urls.md

index.md

search.md

tile.json