Skip to content

SOP 004: Query a Database

Fresh Last Updated: January 2025
FieldValue
SOP IDSOP-004
Version1.0
StatusActive
DifficultyIntermediate

Purpose

Query Notion databases with filters, sorts, and pagination to retrieve specific data efficiently.

Prerequisites

  • [ ] Database ID from SOP 003
  • [ ] Read content capability enabled
  • [ ] Database shared with integration

Query Flow

Step-by-Step Instructions

Step 1: Basic Query

javascript
const { Client } = require('@notionhq/client');
require('dotenv').config();

const notion = new Client({ auth: process.env.NOTION_API_KEY });

async function queryDatabase() {
  const response = await notion.databases.query({
    database_id: process.env.NOTION_DATABASE_ID
  });

  console.log(`Found ${response.results.length} pages`);
  return response.results;
}

Step 2: Query with Filters

Simple Filter

javascript
// Filter by single select
const response = await notion.databases.query({
  database_id: databaseId,
  filter: {
    property: 'Status',
    select: {
      equals: 'In Progress'
    }
  }
});

Compound Filter (AND)

javascript
const response = await notion.databases.query({
  database_id: databaseId,
  filter: {
    and: [
      {
        property: 'Status',
        select: { equals: 'In Progress' }
      },
      {
        property: 'Priority',
        number: { greater_than: 2 }
      }
    ]
  }
});

Compound Filter (OR)

javascript
const response = await notion.databases.query({
  database_id: databaseId,
  filter: {
    or: [
      {
        property: 'Status',
        select: { equals: 'Done' }
      },
      {
        property: 'Archived',
        checkbox: { equals: true }
      }
    ]
  }
});

Step 3: Filter Types Reference

javascript
// Contains
{ property: 'Name', title: { contains: 'urgent' } }

// Starts with
{ property: 'Name', title: { starts_with: 'TASK-' } }

// Is not empty
{ property: 'Description', rich_text: { is_not_empty: true } }
javascript
// Greater than
{ property: 'Priority', number: { greater_than: 3 } }

// Between (using AND)
{
  and: [
    { property: 'Price', number: { greater_than_or_equal_to: 10 } },
    { property: 'Price', number: { less_than_or_equal_to: 100 } }
  ]
}
javascript
// After specific date
{ property: 'Due Date', date: { after: '2025-01-01' } }

// Within past week
{ property: 'Created', date: { past_week: {} } }

// Is empty (no date set)
{ property: 'Due Date', date: { is_empty: true } }
javascript
// Single select equals
{ property: 'Status', select: { equals: 'Done' } }

// Multi-select contains
{ property: 'Tags', multi_select: { contains: 'Urgent' } }
javascript
// Checked
{ property: 'Archived', checkbox: { equals: true } }

// Not checked
{ property: 'Archived', checkbox: { equals: false } }

Step 4: Sorting Results

javascript
const response = await notion.databases.query({
  database_id: databaseId,
  sorts: [
    {
      property: 'Priority',
      direction: 'ascending'  // or 'descending'
    },
    {
      property: 'Due Date',
      direction: 'ascending'
    }
  ]
});

Sort by Timestamps

javascript
sorts: [
  {
    timestamp: 'created_time',
    direction: 'descending'
  }
]
// Or: 'last_edited_time'

Step 5: Pagination

javascript
async function getAllPages(databaseId, filter = undefined) {
  const pages = [];
  let hasMore = true;
  let cursor = undefined;

  while (hasMore) {
    const response = await notion.databases.query({
      database_id: databaseId,
      filter,
      start_cursor: cursor,
      page_size: 100  // Max 100
    });

    pages.push(...response.results);
    hasMore = response.has_more;
    cursor = response.next_cursor;

    console.log(`Fetched ${pages.length} pages...`);
  }

  return pages;
}

// Usage
const allTasks = await getAllPages(databaseId, {
  property: 'Status',
  select: { does_not_equal: 'Archived' }
});

Step 6: Extract Property Values

javascript
function extractProperties(page) {
  const props = page.properties;

  return {
    id: page.id,
    // Title
    name: props.Name?.title?.[0]?.plain_text || '',
    // Select
    status: props.Status?.select?.name || null,
    // Number
    priority: props.Priority?.number || 0,
    // Date
    dueDate: props['Due Date']?.date?.start || null,
    // Multi-select
    tags: props.Tags?.multi_select?.map(t => t.name) || [],
    // Checkbox
    archived: props.Archived?.checkbox || false,
    // People
    assignees: props.Assignee?.people?.map(p => p.name) || [],
    // URL
    url: page.url
  };
}

// Usage
const pages = await queryDatabase();
const tasks = pages.map(extractProperties);
console.table(tasks);

Complete Example

javascript
async function getActiveTasks() {
  const response = await notion.databases.query({
    database_id: process.env.NOTION_DATABASE_ID,
    filter: {
      and: [
        {
          property: 'Status',
          select: { does_not_equal: 'Done' }
        },
        {
          property: 'Archived',
          checkbox: { equals: false }
        }
      ]
    },
    sorts: [
      { property: 'Priority', direction: 'ascending' },
      { property: 'Due Date', direction: 'ascending' }
    ],
    page_size: 50
  });

  return response.results.map(page => ({
    name: page.properties.Name.title[0]?.plain_text,
    status: page.properties.Status.select?.name,
    priority: page.properties.Priority.number,
    dueDate: page.properties['Due Date'].date?.start,
    url: page.url
  }));
}

Verification Checklist

  • [ ] Basic query returns results
  • [ ] Filters reduce result set correctly
  • [ ] Sorts order results as expected
  • [ ] Pagination retrieves all pages
  • [ ] Property extraction works for all types

Troubleshooting

IssueCauseSolution
Empty resultsFilter too restrictiveBroaden filter conditions
Wrong propertyProperty name mismatchCheck exact property names
Missing dataPagination not implementedUse has_more and next_cursor
Type errorWrong filter typeMatch filter to property type

See Also

Built with VitePress | Powered by Claude AI