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-c222dcd48bb2Creating 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
- Always validate input before sending to Notion API
- Use pagination for large datasets
- Implement retry logic for rate limits
- Cache frequently accessed data (categories, users)
- Log all API calls for debugging
- Handle errors gracefully with fallbacks
- Test with mock client before production
- Monitor API usage to stay within limits