Skip to content

Notion API Examples

This guide provides practical code examples for working with the Notion API in the Yappa Knowledge Hub project.

Setup

NotionClient Initialization

php
use App\Service\Notion\NotionClient;

$notionClient = new NotionClient(
    apiKey: $_ENV['NOTION_API_KEY'],
    version: $_ENV['NOTION_VERSION']
);

Environment Configuration

bash
NOTION_API_KEY=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
NOTION_VERSION=2022-06-28
NOTION_DATABASE_KNOWLEDGE=306e292a-15d5-8004-a8cb-c222dcd48bb2

Creating Pages

Create Knowledge Item

php
$properties = [
    'Titel' => [
        'title' => [
            ['text' => ['content' => 'New Knowledge Item']]
        ]
    ],
    'URL' => [
        'url' => 'https://example.com/article'
    ],
    'Samenvatting' => [
        'rich_text' => [
            ['text' => ['content' => 'This is a summary of the article...']]
        ]
    ],
    'Doelgroepen' => [
        'multi_select' => [
            ['name' => 'Bestuur'],
            ['name' => 'Leden']
        ]
    ],
    'Tags' => [
        'multi_select' => [
            ['name' => 'technology'],
            ['name' => 'innovation']
        ]
    ],
    'Bron' => [
        'select' => ['name' => 'Slack']
    ],
    'Datum toegevoegd' => [
        'date' => ['start' => date('c')]
    ]
];

$response = $notionClient->createPage(
    databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE'],
    properties: $properties
);

$pageId = $response['id'];

Create Category

php
$properties = [
    'Naam' => [
        'title' => [
            ['text' => ['content' => 'Technology Updates']]
        ]
    ],
    'Beschrijving' => [
        'rich_text' => [
            ['text' => ['content' => 'Latest technology news and updates']]
        ]
    ],
    'Doelgroepen' => [
        'multi_select' => [
            ['name' => 'Leden'],
            ['name' => 'Commissies']
        ]
    ]
];

$response = $notionClient->createPage(
    databaseId: $_ENV['NOTION_DATABASE_CATEGORIES'],
    properties: $properties
);

Updating Pages

Update Knowledge Item

php
$pageId = '306e292a-15d5-8004-a8cb-c222dcd48bb2';

$updates = [
    'Samenvatting' => [
        'rich_text' => [
            ['text' => ['content' => 'Updated summary with more details...']]
        ]
    ],
    'Tags' => [
        'multi_select' => [
            ['name' => 'updated'],
            ['name' => 'reviewed']
        ]
    ]
];

$response = $notionClient->updatePage($pageId, $updates);

Add Relation

php
// Link knowledge item to category
$updates = [
    'Thematische Lijst' => [
        'relation' => [
            ['id' => $categoryPageId]
        ]
    ]
];

$notionClient->updatePage($knowledgePageId, $updates);

Querying Databases

Query All Items

php
$response = $notionClient->queryDatabase(
    databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE']
);

foreach ($response['results'] as $page) {
    $title = $page['properties']['Titel']['title'][0]['text']['content'] ?? '';
    $url = $page['properties']['URL']['url'] ?? '';

    echo "Title: $title\n";
    echo "URL: $url\n\n";
}

Query with Filter

php
// Find items added in the last 7 days
$filter = [
    'property' => 'Datum toegevoegd',
    'date' => [
        'after' => date('c', strtotime('-7 days'))
    ]
];

$response = $notionClient->queryDatabase(
    databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE'],
    filter: $filter
);

Query with Multiple Filters

php
// Find items with specific tag and target group
$filter = [
    'and' => [
        [
            'property' => 'Tags',
            'multi_select' => [
                'contains' => 'technology'
            ]
        ],
        [
            'property' => 'Doelgroepen',
            'multi_select' => [
                'contains' => 'Bestuur'
            ]
        ]
    ]
];

$response = $notionClient->queryDatabase(
    databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE'],
    filter: $filter
);

Query with Sorting

php
$sorts = [
    [
        'property' => 'Datum toegevoegd',
        'direction' => 'descending'
    ]
];

$response = $notionClient->queryDatabase(
    databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE'],
    sorts: $sorts
);

Pagination

Fetch All Pages

php
$allPages = [];
$hasMore = true;
$startCursor = null;

while ($hasMore) {
    $response = $notionClient->queryDatabase(
        databaseId: $_ENV['NOTION_DATABASE_KNOWLEDGE'],
        startCursor: $startCursor,
        pageSize: 100
    );

    $allPages = array_merge($allPages, $response['results']);

    $hasMore = $response['has_more'];
    $startCursor = $response['next_cursor'] ?? null;
}

echo "Total pages: " . count($allPages) . "\n";

Property Extraction

Extract Title

php
function extractTitle(array $page): string
{
    return $page['properties']['Titel']['title'][0]['text']['content'] ?? '';
}

Extract Rich Text

php
function extractRichText(array $page, string $propertyName): string
{
    $richText = $page['properties'][$propertyName]['rich_text'] ?? [];

    return implode('', array_map(
        fn($block) => $block['text']['content'] ?? '',
        $richText
    ));
}

Extract Multi-Select

php
function extractMultiSelect(array $page, string $propertyName): array
{
    $multiSelect = $page['properties'][$propertyName]['multi_select'] ?? [];

    return array_map(
        fn($option) => $option['name'],
        $multiSelect
    );
}

Extract Relation

php
function extractRelation(array $page, string $propertyName): array
{
    $relation = $page['properties'][$propertyName]['relation'] ?? [];

    return array_map(
        fn($rel) => $rel['id'],
        $relation
    );
}

Extract Date

php
function extractDate(array $page, string $propertyName): ?DateTime
{
    $date = $page['properties'][$propertyName]['date']['start'] ?? null;

    return $date ? new DateTime($date) : null;
}

Complete Example: Sync Service

php
namespace App\Service\Notion;

use App\Entity\Resource;
use Doctrine\ORM\EntityManagerInterface;

class NotionSyncService
{
    public function __construct(
        private NotionClient $notionClient,
        private EntityManagerInterface $entityManager,
        private string $knowledgeDatabaseId
    ) {}

    public function pushToNotion(Resource $resource): string
    {
        $properties = $this->mapResourceToProperties($resource);

        if ($resource->getNotionId()) {
            // Update existing page
            $this->notionClient->updatePage(
                $resource->getNotionId(),
                $properties
            );
            return $resource->getNotionId();
        } else {
            // Create new page
            $response = $this->notionClient->createPage(
                $this->knowledgeDatabaseId,
                $properties
            );
            return $response['id'];
        }
    }

    public function syncFromNotion(): int
    {
        $count = 0;
        $hasMore = true;
        $startCursor = null;

        while ($hasMore) {
            $response = $this->notionClient->queryDatabase(
                databaseId: $this->knowledgeDatabaseId,
                startCursor: $startCursor,
                pageSize: 100
            );

            foreach ($response['results'] as $page) {
                $this->syncPage($page);
                $count++;
            }

            $hasMore = $response['has_more'];
            $startCursor = $response['next_cursor'] ?? null;
        }

        $this->entityManager->flush();

        return $count;
    }

    private function syncPage(array $page): void
    {
        $notionId = $page['id'];

        $resource = $this->entityManager
            ->getRepository(Resource::class)
            ->findOneBy(['notionId' => $notionId]);

        if (!$resource) {
            $resource = new Resource();
            $resource->setNotionId($notionId);
            $this->entityManager->persist($resource);
        }

        $this->mapPropertiesToResource($page, $resource);
    }

    private function mapResourceToProperties(Resource $resource): array
    {
        return [
            'Titel' => [
                'title' => [
                    ['text' => ['content' => $resource->getTitle()]]
                ]
            ],
            'URL' => [
                'url' => $resource->getUrl()
            ],
            'Samenvatting' => [
                'rich_text' => [
                    ['text' => ['content' => $resource->getSummary() ?? '']]
                ]
            ],
            'Doelgroepen' => [
                'multi_select' => array_map(
                    fn($group) => ['name' => $group],
                    $resource->getTargetGroups()
                )
            ],
            'Tags' => [
                'multi_select' => array_map(
                    fn($tag) => ['name' => $tag->getName()],
                    $resource->getTags()->toArray()
                )
            ]
        ];
    }

    private function mapPropertiesToResource(array $page, Resource $resource): void
    {
        $props = $page['properties'];

        $resource->setTitle(
            $props['Titel']['title'][0]['text']['content'] ?? ''
        );

        $resource->setUrl(
            $props['URL']['url'] ?? null
        );

        $resource->setSummary(
            $this->extractRichText($props, 'Samenvatting')
        );

        $resource->setTargetGroups(
            $this->extractMultiSelect($props, 'Doelgroepen')
        );
    }

    private function extractRichText(array $props, string $name): string
    {
        $richText = $props[$name]['rich_text'] ?? [];
        return implode('', array_map(
            fn($block) => $block['text']['content'] ?? '',
            $richText
        ));
    }

    private function extractMultiSelect(array $props, string $name): array
    {
        $multiSelect = $props[$name]['multi_select'] ?? [];
        return array_map(
            fn($option) => $option['name'],
            $multiSelect
        );
    }
}

Error Handling

Retry Logic

php
function queryWithRetry(NotionClient $client, string $databaseId, int $maxRetries = 3): array
{
    $attempt = 0;

    while ($attempt < $maxRetries) {
        try {
            return $client->queryDatabase($databaseId);
        } catch (NotionApiException $e) {
            if ($e->getCode() === 429) {
                // Rate limit - wait and retry
                $retryAfter = $e->getRetryAfter() ?? 1;
                sleep($retryAfter);
                $attempt++;
            } else {
                throw $e;
            }
        }
    }

    throw new Exception("Failed after $maxRetries attempts");
}

Validation

php
function validateProperties(array $properties): void
{
    if (!isset($properties['Titel']['title'][0]['text']['content'])) {
        throw new InvalidArgumentException('Title is required');
    }

    $title = $properties['Titel']['title'][0]['text']['content'];
    if (strlen($title) > 2000) {
        throw new InvalidArgumentException('Title too long (max 2000 chars)');
    }

    if (isset($properties['URL']['url'])) {
        $url = $properties['URL']['url'];
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            throw new InvalidArgumentException('Invalid URL format');
        }
    }
}

Testing

Mock Notion Client

php
class MockNotionClient extends NotionClient
{
    private array $pages = [];

    public function createPage(string $databaseId, array $properties): array
    {
        $pageId = uniqid('page_');
        $this->pages[$pageId] = [
            'id' => $pageId,
            'properties' => $properties
        ];
        return $this->pages[$pageId];
    }

    public function queryDatabase(string $databaseId, array $options = []): array
    {
        return [
            'results' => array_values($this->pages),
            'has_more' => false,
            'next_cursor' => null
        ];
    }
}

Unit Test Example

php
use PHPUnit\Framework\TestCase;

class NotionSyncServiceTest extends TestCase
{
    public function testPushToNotion(): void
    {
        $mockClient = new MockNotionClient('test_key', '2022-06-28');
        $service = new NotionSyncService($mockClient, $this->entityManager, 'db_123');

        $resource = new Resource();
        $resource->setTitle('Test Item');
        $resource->setUrl('https://example.com');

        $notionId = $service->pushToNotion($resource);

        $this->assertNotEmpty($notionId);
        $this->assertEquals($notionId, $resource->getNotionId());
    }
}

Best Practices

  1. Always validate input before sending to Notion API
  2. Use pagination for large datasets
  3. Implement retry logic for rate limits
  4. Cache frequently accessed data (categories, users)
  5. Log all API calls for debugging
  6. Handle errors gracefully with fallbacks
  7. Test with mock client before production
  8. Monitor API usage to stay within limits

Next Steps