Query Notion Database
Query a Notion database with filters and sorts, handling pagination automatically
Source Code
import fs from "fs";
const [
databaseId,
filterJson = "",
sortsJson = "",
maxResults = "100",
outputPath = "session/notion-query.json",
] = process.argv.slice(2);
if (!databaseId) {
console.error("Error: databaseId is required");
process.exit(1);
}
const maxResultsNum = Math.min(parseInt(maxResults) || 100, 500);
// Parse filter and sorts if provided
let filter = null;
let sorts = null;
if (filterJson) {
try {
filter = JSON.parse(filterJson);
} catch (e) {
console.error("Error parsing filter JSON:", e.message);
process.exit(1);
}
}
if (sortsJson) {
try {
sorts = JSON.parse(sortsJson);
} catch (e) {
console.error("Error parsing sorts JSON:", e.message);
process.exit(1);
}
}
console.log(`Querying database ${databaseId}...`);
if (filter) console.log(` Filter: ${JSON.stringify(filter)}`);
if (sorts) console.log(` Sort: ${JSON.stringify(sorts)}`);
const NOTION_API = "https://api.notion.com/v1";
const headers = {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
};
/**
* Extract readable value from a Notion property
*/
function extractPropertyValue(prop) {
if (!prop) return null;
switch (prop.type) {
case "title":
return prop.title?.map((t) => t.plain_text).join("") || "";
case "rich_text":
return prop.rich_text?.map((t) => t.plain_text).join("") || "";
case "number":
return prop.number;
case "select":
return prop.select?.name || null;
case "multi_select":
return prop.multi_select?.map((s) => s.name) || [];
case "date":
if (!prop.date) return null;
return prop.date.end
? { start: prop.date.start, end: prop.date.end }
: prop.date.start;
case "people":
return prop.people?.map((p) => p.name || p.id) || [];
case "files":
return (
prop.files?.map((f) => ({
name: f.name,
url: f.file?.url || f.external?.url,
})) || []
);
case "checkbox":
return prop.checkbox;
case "url":
return prop.url;
case "email":
return prop.email;
case "phone_number":
return prop.phone_number;
case "formula":
return prop.formula?.[prop.formula.type];
case "relation":
return prop.relation?.map((r) => r.id) || [];
case "rollup":
if (prop.rollup?.type === "array") {
return prop.rollup.array?.map((item) => extractPropertyValue(item));
}
return prop.rollup?.[prop.rollup.type];
case "status":
return prop.status?.name || null;
case "created_time":
return prop.created_time;
case "last_edited_time":
return prop.last_edited_time;
case "created_by":
return prop.created_by?.name || prop.created_by?.id;
case "last_edited_by":
return prop.last_edited_by?.name || prop.last_edited_by?.id;
case "unique_id":
return prop.unique_id?.prefix
? `${prop.unique_id.prefix}-${prop.unique_id.number}`
: prop.unique_id?.number;
default:
return null;
}
}
try {
// First, get database schema
const dbRes = await fetch(`${NOTION_API}/databases/${databaseId}`, {
method: "GET",
headers,
});
if (!dbRes.ok) {
const errorText = await dbRes.text();
console.error(`Failed to retrieve database: ${dbRes.status}`);
console.error(errorText);
throw new Error(`Failed to retrieve database: ${dbRes.status}`);
}
const dbInfo = await dbRes.json();
const dbTitle = dbInfo.title?.[0]?.plain_text || "Untitled Database";
const schema = {};
for (const [name, prop] of Object.entries(dbInfo.properties || {})) {
schema[name] = {
type: prop.type,
...(prop.select && { options: prop.select.options?.map((o) => o.name) }),
...(prop.multi_select && {
options: prop.multi_select.options?.map((o) => o.name),
}),
...(prop.status && {
options: prop.status.options?.map((o) => o.name),
groups: prop.status.groups?.map((g) => g.name),
}),
};
}
console.log(` Database: ${dbTitle}`);
console.log(` Properties: ${Object.keys(schema).join(", ")}`);
// Query the database
const entries = [];
let cursor = undefined;
let totalFetched = 0;
while (totalFetched < maxResultsNum) {
const pageSize = Math.min(maxResultsNum - totalFetched, 100);
const body = {
page_size: pageSize,
...(filter && { filter }),
...(sorts && { sorts }),
...(cursor && { start_cursor: cursor }),
};
const queryRes = await fetch(
`${NOTION_API}/databases/${databaseId}/query`,
{
method: "POST",
headers,
body: JSON.stringify(body),
}
);
if (!queryRes.ok) {
const errorText = await queryRes.text();
console.error(`Query failed: ${queryRes.status}`);
console.error(errorText);
throw new Error(`Query failed: ${queryRes.status}`);
}
const response = await queryRes.json();
for (const page of response.results) {
if (totalFetched >= maxResultsNum) break;
const entry = {
id: page.id,
url: page.url,
createdTime: page.created_time,
lastEditedTime: page.last_edited_time,
properties: {},
};
// Extract icon
if (page.icon) {
entry.icon =
page.icon.type === "emoji"
? page.icon.emoji
: page.icon.external?.url || page.icon.file?.url;
}
// Extract all property values
for (const [name, prop] of Object.entries(page.properties || {})) {
entry.properties[name] = extractPropertyValue(prop);
}
entries.push(entry);
totalFetched++;
}
if (!response.has_more || totalFetched >= maxResultsNum) break;
cursor = response.next_cursor;
console.log(` Fetched ${totalFetched} entries...`);
}
// Ensure output directory exists
const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
if (dir) {
fs.mkdirSync(dir, { recursive: true });
}
const output = {
databaseId,
databaseTitle: dbTitle,
schema,
filter: filter || null,
sorts: sorts || null,
queriedAt: new Date().toISOString(),
count: entries.length,
entries,
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
// Log summary
console.log(`\nā Found ${entries.length} entries`);
console.log(` Written to: ${outputPath}`);
// Show first few entries
if (entries.length > 0) {
console.log(`\n Sample entries:`);
for (const entry of entries.slice(0, 3)) {
// Find title property
const titleProp = Object.entries(entry.properties).find(
([k]) => schema[k]?.type === "title"
);
const title = titleProp ? titleProp[1] : "Untitled";
console.log(` ${entry.icon || "š"} ${title}`);
}
if (entries.length > 3) {
console.log(` ... and ${entries.length - 3} more`);
}
}
console.log(
JSON.stringify({
success: true,
outputPath,
databaseTitle: dbTitle,
count: entries.length,
propertyCount: Object.keys(schema).length,
})
);
} catch (error) {
console.error("Error querying database:", error.message);
throw error;
} import fs from "fs";
const [
databaseId,
filterJson = "",
sortsJson = "",
maxResults = "100",
outputPath = "session/notion-query.json",
] = process.argv.slice(2);
if (!databaseId) {
console.error("Error: databaseId is required");
process.exit(1);
}
const maxResultsNum = Math.min(parseInt(maxResults) || 100, 500);
// Parse filter and sorts if provided
let filter = null;
let sorts = null;
if (filterJson) {
try {
filter = JSON.parse(filterJson);
} catch (e) {
console.error("Error parsing filter JSON:", e.message);
process.exit(1);
}
}
if (sortsJson) {
try {
sorts = JSON.parse(sortsJson);
} catch (e) {
console.error("Error parsing sorts JSON:", e.message);
process.exit(1);
}
}
console.log(`Querying database ${databaseId}...`);
if (filter) console.log(` Filter: ${JSON.stringify(filter)}`);
if (sorts) console.log(` Sort: ${JSON.stringify(sorts)}`);
const NOTION_API = "https://api.notion.com/v1";
const headers = {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
};
/**
* Extract readable value from a Notion property
*/
function extractPropertyValue(prop) {
if (!prop) return null;
switch (prop.type) {
case "title":
return prop.title?.map((t) => t.plain_text).join("") || "";
case "rich_text":
return prop.rich_text?.map((t) => t.plain_text).join("") || "";
case "number":
return prop.number;
case "select":
return prop.select?.name || null;
case "multi_select":
return prop.multi_select?.map((s) => s.name) || [];
case "date":
if (!prop.date) return null;
return prop.date.end
? { start: prop.date.start, end: prop.date.end }
: prop.date.start;
case "people":
return prop.people?.map((p) => p.name || p.id) || [];
case "files":
return (
prop.files?.map((f) => ({
name: f.name,
url: f.file?.url || f.external?.url,
})) || []
);
case "checkbox":
return prop.checkbox;
case "url":
return prop.url;
case "email":
return prop.email;
case "phone_number":
return prop.phone_number;
case "formula":
return prop.formula?.[prop.formula.type];
case "relation":
return prop.relation?.map((r) => r.id) || [];
case "rollup":
if (prop.rollup?.type === "array") {
return prop.rollup.array?.map((item) => extractPropertyValue(item));
}
return prop.rollup?.[prop.rollup.type];
case "status":
return prop.status?.name || null;
case "created_time":
return prop.created_time;
case "last_edited_time":
return prop.last_edited_time;
case "created_by":
return prop.created_by?.name || prop.created_by?.id;
case "last_edited_by":
return prop.last_edited_by?.name || prop.last_edited_by?.id;
case "unique_id":
return prop.unique_id?.prefix
? `${prop.unique_id.prefix}-${prop.unique_id.number}`
: prop.unique_id?.number;
default:
return null;
}
}
try {
// First, get database schema
const dbRes = await fetch(`${NOTION_API}/databases/${databaseId}`, {
method: "GET",
headers,
});
if (!dbRes.ok) {
const errorText = await dbRes.text();
console.error(`Failed to retrieve database: ${dbRes.status}`);
console.error(errorText);
throw new Error(`Failed to retrieve database: ${dbRes.status}`);
}
const dbInfo = await dbRes.json();
const dbTitle = dbInfo.title?.[0]?.plain_text || "Untitled Database";
const schema = {};
for (const [name, prop] of Object.entries(dbInfo.properties || {})) {
schema[name] = {
type: prop.type,
...(prop.select && { options: prop.select.options?.map((o) => o.name) }),
...(prop.multi_select && {
options: prop.multi_select.options?.map((o) => o.name),
}),
...(prop.status && {
options: prop.status.options?.map((o) => o.name),
groups: prop.status.groups?.map((g) => g.name),
}),
};
}
console.log(` Database: ${dbTitle}`);
console.log(` Properties: ${Object.keys(schema).join(", ")}`);
// Query the database
const entries = [];
let cursor = undefined;
let totalFetched = 0;
while (totalFetched < maxResultsNum) {
const pageSize = Math.min(maxResultsNum - totalFetched, 100);
const body = {
page_size: pageSize,
...(filter && { filter }),
...(sorts && { sorts }),
...(cursor && { start_cursor: cursor }),
};
const queryRes = await fetch(
`${NOTION_API}/databases/${databaseId}/query`,
{
method: "POST",
headers,
body: JSON.stringify(body),
}
);
if (!queryRes.ok) {
const errorText = await queryRes.text();
console.error(`Query failed: ${queryRes.status}`);
console.error(errorText);
throw new Error(`Query failed: ${queryRes.status}`);
}
const response = await queryRes.json();
for (const page of response.results) {
if (totalFetched >= maxResultsNum) break;
const entry = {
id: page.id,
url: page.url,
createdTime: page.created_time,
lastEditedTime: page.last_edited_time,
properties: {},
};
// Extract icon
if (page.icon) {
entry.icon =
page.icon.type === "emoji"
? page.icon.emoji
: page.icon.external?.url || page.icon.file?.url;
}
// Extract all property values
for (const [name, prop] of Object.entries(page.properties || {})) {
entry.properties[name] = extractPropertyValue(prop);
}
entries.push(entry);
totalFetched++;
}
if (!response.has_more || totalFetched >= maxResultsNum) break;
cursor = response.next_cursor;
console.log(` Fetched ${totalFetched} entries...`);
}
// Ensure output directory exists
const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
if (dir) {
fs.mkdirSync(dir, { recursive: true });
}
const output = {
databaseId,
databaseTitle: dbTitle,
schema,
filter: filter || null,
sorts: sorts || null,
queriedAt: new Date().toISOString(),
count: entries.length,
entries,
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
// Log summary
console.log(`\nā Found ${entries.length} entries`);
console.log(` Written to: ${outputPath}`);
// Show first few entries
if (entries.length > 0) {
console.log(`\n Sample entries:`);
for (const entry of entries.slice(0, 3)) {
// Find title property
const titleProp = Object.entries(entry.properties).find(
([k]) => schema[k]?.type === "title"
);
const title = titleProp ? titleProp[1] : "Untitled";
console.log(` ${entry.icon || "š"} ${title}`);
}
if (entries.length > 3) {
console.log(` ... and ${entries.length - 3} more`);
}
}
console.log(
JSON.stringify({
success: true,
outputPath,
databaseTitle: dbTitle,
count: entries.length,
propertyCount: Object.keys(schema).length,
})
);
} catch (error) {
console.error("Error querying database:", error.message);
throw error;
}