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=ntn_your-integration-key
NOTION_VERSION=2022-06-28
NOTION_DATABASE_KNOWLEDGE=your-knowledge-database-id

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 ​