code icon Code

Search Notion Workspace

Search for pages and databases in Notion workspace by title or content

Source Code

import fs from "fs";

const [
  query = "",
  filter = "all",
  maxResults = "20",
  outputPath = "session/notion-search.json",
] = process.argv.slice(2);

const maxResultsNum = Math.min(parseInt(maxResults) || 20, 100);

console.log(
  `Searching Notion for "${query || "(all)"}"${
    filter !== "all" ? ` (${filter}s only)` : ""
  }...`
);

const NOTION_API = "https://api.notion.com/v1";
const headers = {
  Authorization: "Bearer PLACEHOLDER_TOKEN",
  "Content-Type": "application/json",
  "Notion-Version": "2022-06-28",
};

try {
  const results = [];
  let cursor = undefined;
  let totalFetched = 0;

  while (totalFetched < maxResultsNum) {
    const pageSize = Math.min(maxResultsNum - totalFetched, 100);

    const body = {
      page_size: pageSize,
      ...(query && { query }),
      ...(cursor && { start_cursor: cursor }),
    };

    // Add filter if not "all"
    if (filter === "page") {
      body.filter = { property: "object", value: "page" };
    } else if (filter === "database") {
      body.filter = { property: "object", value: "database" };
    }

    const res = await fetch(`${NOTION_API}/search`, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
    });

    if (!res.ok) {
      const errorText = await res.text();
      console.error(`Notion API error: ${res.status}`);
      console.error(errorText);
      throw new Error(`Notion API failed: ${res.status}`);
    }

    const response = await res.json();

    for (const item of response.results) {
      if (totalFetched >= maxResultsNum) break;

      const result = {
        id: item.id,
        type: item.object,
        url: item.url,
        lastEdited: item.last_edited_time,
        createdTime: item.created_time,
      };

      if (item.object === "page") {
        // Extract title from page properties
        const titleProp = Object.values(item.properties || {}).find(
          (p) => p.type === "title"
        );
        result.title =
          titleProp?.title?.[0]?.plain_text ||
          item.properties?.title?.title?.[0]?.plain_text ||
          "Untitled";

        // Get parent info
        if (item.parent?.type === "database_id") {
          result.parentType = "database";
          result.parentId = item.parent.database_id;
        } else if (item.parent?.type === "page_id") {
          result.parentType = "page";
          result.parentId = item.parent.page_id;
        } else {
          result.parentType = "workspace";
        }

        // Include icon if present
        if (item.icon) {
          result.icon =
            item.icon.type === "emoji"
              ? item.icon.emoji
              : item.icon.external?.url || item.icon.file?.url;
        }
      } else if (item.object === "database") {
        result.title = item.title?.[0]?.plain_text || "Untitled Database";
        result.description = item.description?.[0]?.plain_text || null;

        // Get property schema summary
        result.properties = Object.entries(item.properties || {}).map(
          ([name, prop]) => ({
            name,
            type: prop.type,
          })
        );

        if (item.icon) {
          result.icon =
            item.icon.type === "emoji"
              ? item.icon.emoji
              : item.icon.external?.url || item.icon.file?.url;
        }
      }

      results.push(result);
      totalFetched++;
    }

    if (!response.has_more || totalFetched >= maxResultsNum) break;
    cursor = response.next_cursor;
  }

  // Ensure output directory exists
  const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
  if (dir) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const output = {
    query: query || null,
    filter,
    searchedAt: new Date().toISOString(),
    count: results.length,
    results,
  };

  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  // Log summary
  console.log(`\nāœ“ Found ${results.length} results`);
  console.log(`  Written to: ${outputPath}`);

  // Show top results
  const pages = results.filter((r) => r.type === "page");
  const databases = results.filter((r) => r.type === "database");

  if (databases.length > 0) {
    console.log(`\n  Databases (${databases.length}):`);
    for (const db of databases.slice(0, 5)) {
      console.log(
        `    ${db.icon || "šŸ“Š"} ${db.title} (${
          db.properties?.length || 0
        } properties)`
      );
    }
  }

  if (pages.length > 0) {
    console.log(`\n  Pages (${pages.length}):`);
    for (const page of pages.slice(0, 5)) {
      console.log(`    ${page.icon || "šŸ“„"} ${page.title}`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      count: results.length,
      databases: databases.length,
      pages: pages.length,
    })
  );
} catch (error) {
  console.error("Error searching Notion:", error.message);
  throw error;
}
import fs from "fs";

const [
  query = "",
  filter = "all",
  maxResults = "20",
  outputPath = "session/notion-search.json",
] = process.argv.slice(2);

const maxResultsNum = Math.min(parseInt(maxResults) || 20, 100);

console.log(
  `Searching Notion for "${query || "(all)"}"${
    filter !== "all" ? ` (${filter}s only)` : ""
  }...`
);

const NOTION_API = "https://api.notion.com/v1";
const headers = {
  Authorization: "Bearer PLACEHOLDER_TOKEN",
  "Content-Type": "application/json",
  "Notion-Version": "2022-06-28",
};

try {
  const results = [];
  let cursor = undefined;
  let totalFetched = 0;

  while (totalFetched < maxResultsNum) {
    const pageSize = Math.min(maxResultsNum - totalFetched, 100);

    const body = {
      page_size: pageSize,
      ...(query && { query }),
      ...(cursor && { start_cursor: cursor }),
    };

    // Add filter if not "all"
    if (filter === "page") {
      body.filter = { property: "object", value: "page" };
    } else if (filter === "database") {
      body.filter = { property: "object", value: "database" };
    }

    const res = await fetch(`${NOTION_API}/search`, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
    });

    if (!res.ok) {
      const errorText = await res.text();
      console.error(`Notion API error: ${res.status}`);
      console.error(errorText);
      throw new Error(`Notion API failed: ${res.status}`);
    }

    const response = await res.json();

    for (const item of response.results) {
      if (totalFetched >= maxResultsNum) break;

      const result = {
        id: item.id,
        type: item.object,
        url: item.url,
        lastEdited: item.last_edited_time,
        createdTime: item.created_time,
      };

      if (item.object === "page") {
        // Extract title from page properties
        const titleProp = Object.values(item.properties || {}).find(
          (p) => p.type === "title"
        );
        result.title =
          titleProp?.title?.[0]?.plain_text ||
          item.properties?.title?.title?.[0]?.plain_text ||
          "Untitled";

        // Get parent info
        if (item.parent?.type === "database_id") {
          result.parentType = "database";
          result.parentId = item.parent.database_id;
        } else if (item.parent?.type === "page_id") {
          result.parentType = "page";
          result.parentId = item.parent.page_id;
        } else {
          result.parentType = "workspace";
        }

        // Include icon if present
        if (item.icon) {
          result.icon =
            item.icon.type === "emoji"
              ? item.icon.emoji
              : item.icon.external?.url || item.icon.file?.url;
        }
      } else if (item.object === "database") {
        result.title = item.title?.[0]?.plain_text || "Untitled Database";
        result.description = item.description?.[0]?.plain_text || null;

        // Get property schema summary
        result.properties = Object.entries(item.properties || {}).map(
          ([name, prop]) => ({
            name,
            type: prop.type,
          })
        );

        if (item.icon) {
          result.icon =
            item.icon.type === "emoji"
              ? item.icon.emoji
              : item.icon.external?.url || item.icon.file?.url;
        }
      }

      results.push(result);
      totalFetched++;
    }

    if (!response.has_more || totalFetched >= maxResultsNum) break;
    cursor = response.next_cursor;
  }

  // Ensure output directory exists
  const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
  if (dir) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const output = {
    query: query || null,
    filter,
    searchedAt: new Date().toISOString(),
    count: results.length,
    results,
  };

  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  // Log summary
  console.log(`\nāœ“ Found ${results.length} results`);
  console.log(`  Written to: ${outputPath}`);

  // Show top results
  const pages = results.filter((r) => r.type === "page");
  const databases = results.filter((r) => r.type === "database");

  if (databases.length > 0) {
    console.log(`\n  Databases (${databases.length}):`);
    for (const db of databases.slice(0, 5)) {
      console.log(
        `    ${db.icon || "šŸ“Š"} ${db.title} (${
          db.properties?.length || 0
        } properties)`
      );
    }
  }

  if (pages.length > 0) {
    console.log(`\n  Pages (${pages.length}):`);
    for (const page of pages.slice(0, 5)) {
      console.log(`    ${page.icon || "šŸ“„"} ${page.title}`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      count: results.length,
      databases: databases.length,
      pages: pages.length,
    })
  );
} catch (error) {
  console.error("Error searching Notion:", error.message);
  throw error;
}