PHPackages                             redberry/md-notion - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. redberry/md-notion

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

redberry/md-notion
==================

Read your notion pages as Markdown in Laravel applications.

1.2.1(1mo ago)81.1kMITPHPPHP ^8.3CI passing

Since Oct 3Pushed 1mo agoCompare

[ Source](https://github.com/RedberryProducts/md-notion)[ Packagist](https://packagist.org/packages/redberry/md-notion)[ Docs](https://github.com/redberryproducts/md-notion)[ RSS](/packages/redberry-md-notion/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (18)Versions (12)Used By (0)

Read your notion pages as Markdown
==================================

[](#read-your-notion-pages-as-markdown)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4f8e40d36d5c335080cea8cbd73daf6e6c6ffb79f6c7228144d90ba3bbd9629b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726564626572727970726f64756374732f6d642d6e6f74696f6e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/redberryproducts/md-notion)[![GitHub Tests Action Status](https://camo.githubusercontent.com/857a84ce29fa046c02eafd8fcd2f828317427d6c5554d8cb4d2e7d72e8b49b21/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f726564626572727970726f64756374732f6d642d6e6f74696f6e2f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/redberryproducts/md-notion/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/29c1619330a0d766b04b788c542b4777ff2148fb00bdbee6c1189aa70b3556f6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f726564626572727970726f64756374732f6d642d6e6f74696f6e2f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/redberryproducts/md-notion/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/6554beebf154048cbe58c7d010f1c8c69a416fe0bc99d277eba33d8edd854427/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726564626572727970726f64756374732f6d642d6e6f74696f6e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/redberryproducts/md-notion)

Read your notion pages as Markdown in Laravel applications

Example:

```
use Redberry\MdNotion\Facades\MdNotion;

$pageId = '263d9316605a806f9e95e1377a46ff3e';

// Get page content as markdown
$markdown = MdNotion::make($pageId)->content()->read();

// Get complete recursive content
$fullContent = MdNotion::make($pageId)->full();
```

Don't forget to star the repo ⭐

#### Table of contents

[](#table-of-contents)

- [Installation](#installation)
- [Configuration](#configuration)
- [Features](#features)
- [Usage](#usage)
- [Page Size &amp; Pagination](#page-size--pagination)
- [Error Handling](#error-handling)
- [Page and Database Objects API](#page-and-database-objects-api)
- [Customization](#customization)
- [Testing](#testing)
- [Security Vulnerabilities](#security-vulnerabilities)
- [Credits](#credits)
- [License](#license)

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require redberry/md-notion
```

You can publish the config file with:

```
php artisan vendor:publish --tag="md-notion-config"
```

This is the contents of the published config file:

```
return [
    /**
     * The Notion API key used for authentication with the Notion API.
     */
    'notion_api_key' => env('NOTION_API_KEY', ''),

    /**
     * Defines the maximum block number that can be fetched in a single request.
     */
    'default_page_size' => env('NOTION_DEFAULT_PAGE_SIZE', 100),

    /**
     * Blade templates for markdown rendering
     */
    'templates' => [
        'page_markdown' => 'md-notion::page-md',
        'full_markdown' => 'md-notion::full-md',
    ],

    /**
     * Block type to adapter class mappings.
     * Customize these to use your own adapters.
     */
    'adapters' => [
        'paragraph' => \Redberry\MdNotion\Adapters\ParagraphAdapter::class,
        'heading_1' => \Redberry\MdNotion\Adapters\HeadingAdapter::class,
        // ... many more block adapters
    ],
];
```

Optionally, you can publish the views:

```
php artisan vendor:publish --tag="md-notion-views"
```

Configuration
-------------

[](#configuration)

Set your Notion API key in your `.env` file:

```
NOTION_API_KEY=your_notion_api_key_here
```

To get your Notion API key:

1. Go to [Notion Developers](https://developers.notion.com/)
2. Create a new integration
3. Copy the API key
4. Add the integration to your Notion pages you want to read

Features
--------

[](#features)

🔄 **Fluent API** - Chain methods for intuitive content fetching
📄 **Page Reading** - Extract Notion pages as clean markdown
🗃️ **Database Support** - Convert Notion databases to markdown tables
🌲 **Recursive Fetching** - Get all nested pages and databases in one call
🎨 **Customizable Templates** - Use Blade templates for markdown output
🧩 **Custom Adapters** - Extend block adapters for specialized content
⚡ **Laravel Integration** - Seamless service provider and facade support
🛠️ **Configurable** - Easy configuration via Laravel config files
📊 **Pagination Support** - Automatic pagination for large pages (100+ blocks)
🚨 **Error Handling** - Typed exceptions for Notion API errors

Usage
-----

[](#usage)

### Basic Page Content

[](#basic-page-content)

Get the content of a single page as markdown:

```
use Redberry\MdNotion\Facades\MdNotion;

$pageId = '263d9316605a806f9e95e1377a46ff3e';
$content = MdNotion::make($pageId)->content()->read();

// Returns: "# Page Title\n\nPage content as markdown..."
```

### Fetch Child Pages

[](#fetch-child-pages)

Get collection of child pages:

```
$pages = MdNotion::make($pageId)->pages();

// Returns: Collection of Page objects
```

*note: Each page has: id, title, content, created\_time, last\_edited\_time, etc. Check [API reference](#page-and-database-objects-api)*

### Fetch Child Databases

[](#fetch-child-databases)

Get collection of child databases:

```
$databases = MdNotion::make($pageId)->databases();

// Returns: Collection of Database objects
```

*Each database has: id, title, description, properties, and table content, etc. Check [API reference](#page-and-database-objects-api)*

### Content with Children

[](#content-with-children)

Get page content including child pages and databases:

```
// Include child pages in Markdown content
$content = MdNotion::make($pageId)
    ->content()
    ->withPages()
    ->read();

// Include child databases as tables in Markdown content
$content = MdNotion::make($pageId)
    ->content()
    ->withDatabases()
    ->read();

// Include both child pages and databases in Markdown content
$content = MdNotion::make($pageId)
    ->content()
    ->withPages()
    ->withDatabases()
    ->read();

// Returns: Formatted markdown with main content + child content sections
```

### Complete Recursive Content

[](#complete-recursive-content)

Get everything recursively (current page + all nested pages and databases):

```
$fullContent = MdNotion::make($pageId)->full();

// Returns: Complete markdown with all nested content
```

`full` is the only method which by default returns database item's content as well. It performs minimum one request per page, so pages with nested content or big databases can hit the memory limits, can be slow or hit limits of notion API.

*⚠️ WARNING: This may make many API requests for pages with deep nesting!*

### Dynamic Page Setting

[](#dynamic-page-setting)

Set page ID dynamically:

```
$mdNotion = MdNotion::make(); // Empty initially
$content = $mdNotion->setPage($pageId)->full();
```

### Get Page Objects

[](#get-page-objects)

Access the raw Page object with all data:

```
$page = MdNotion::make($pageId)
    ->content()
    ->withPages()
    ->withDatabases()
    ->get();

// Returns: Page object with loaded child content
// Access: $page->getTitle(), $page->getContent(), $page->getChildPages(), etc.
```

Page Size &amp; Pagination
--------------------------

[](#page-size--pagination)

The Notion API limits responses to 100 blocks per request. This package handles pagination automatically, allowing you to fetch more blocks seamlessly.

### Configuration

[](#configuration-1)

Set the default page size in your `.env` file:

```
NOTION_DEFAULT_PAGE_SIZE=100
```

Or in the config file:

```
// config/md-notion.php
return [
    'default_page_size' => env('NOTION_DEFAULT_PAGE_SIZE', 100),
    // ...
];
```

### Custom Page Size

[](#custom-page-size)

You can override the default page size per request:

```
use Redberry\MdNotion\Facades\MdNotion;

// Fetch up to 50 blocks
$content = MdNotion::make($pageId)->content()->read(50);

// Fetch up to 200 blocks (automatically paginated)
$content = MdNotion::make($pageId)->content()->read(200);

// Use default from config
$content = MdNotion::make($pageId)->content()->read();
```

### How Pagination Works

[](#how-pagination-works)

- **Page size ≤ 100**: Single API request
- **Page size &gt; 100**: Automatic pagination with multiple requests

The returned data always has a consistent structure:

```
[
    'results' => [...],      // Array of blocks
    'has_more' => bool,      // Whether more items exist
    'next_cursor' => ?string // Cursor for manual continuation (null if results were trimmed)
]
```

> **Note**: When results are trimmed to meet your requested limit, `next_cursor` is set to `null` to prevent accidentally skipping items. The `has_more` flag will still indicate if more items exist.

### Validation

[](#validation)

Page size must be a positive integer. Invalid values will throw an exception:

```
// These will throw InvalidArgumentException:
MdNotion::make($pageId)->content()->read(0);   // Zero not allowed
MdNotion::make($pageId)->content()->read(-5);  // Negative not allowed
```

Error Handling
--------------

[](#error-handling)

The package provides a dedicated `NotionApiException` for handling Notion API errors with detailed information.

### Basic Error Handling

[](#basic-error-handling)

```
use Redberry\MdNotion\Facades\MdNotion;
use Redberry\MdNotion\SDK\Exceptions\NotionApiException;

try {
    $content = MdNotion::make($pageId)->content()->read();
} catch (NotionApiException $e) {
    // Get error details
    echo $e->getMessage();           // "Notion API Error [404] object_not_found: Could not find page..."
    echo $e->getNotionCode();        // "object_not_found"
    echo $e->getNotionMessage();     // "Could not find page with ID: ..."

    // Access the original response
    $response = $e->getResponse();
    $statusCode = $response->status(); // 404
}
```

### Error Type Checking

[](#error-type-checking)

The exception provides convenient methods to check error types:

```
try {
    $content = MdNotion::make($pageId)->content()->read();
} catch (NotionApiException $e) {
    if ($e->isNotFound()) {
        // Page doesn't exist or not shared with integration
    }

    if ($e->isUnauthorized()) {
        // Invalid API key
    }

    if ($e->isForbidden()) {
        // Integration doesn't have access to this resource
    }

    if ($e->isRateLimited()) {
        // Too many requests, implement backoff
    }

    if ($e->isValidationError()) {
        // Invalid request parameters
    }

    if ($e->isServerError()) {
        // Notion server error (5xx)
    }

    if ($e->isRetryable()) {
        // Safe to retry (rate limits, server errors, conflicts)
    }
}
```

### Notion Error Codes

[](#notion-error-codes)

The `getNotionCode()` method returns one of these values:

CodeHTTP StatusDescription`invalid_json`400Request body is not valid JSON`invalid_request_url`400Invalid request URL`invalid_request`400Invalid request parameters`validation_error`400Request validation failed`missing_version`400Missing Notion-Version header`unauthorized`401Invalid API key`restricted_resource`403No access to resource`object_not_found`404Resource not found`conflict_error`409Transaction conflict`rate_limited`429Too many requests`internal_server_error`500Notion server error`bad_gateway`502Bad gateway`service_unavailable`503Service temporarily unavailable`gateway_timeout`504Gateway timeout### Retry Strategy Example

[](#retry-strategy-example)

```
use Redberry\MdNotion\SDK\Exceptions\NotionApiException;

function fetchWithRetry(string $pageId, int $maxRetries = 3): string
{
    $attempts = 0;

    while ($attempts < $maxRetries) {
        try {
            return MdNotion::make($pageId)->content()->read();
        } catch (NotionApiException $e) {
            if (!$e->isRetryable()) {
                throw $e; // Don't retry non-retryable errors
            }

            $attempts++;
            if ($attempts >= $maxRetries) {
                throw $e;
            }

            // Exponential backoff
            $delay = $e->isRateLimited() ? 1000 : 500;
            usleep($delay * $attempts * 1000);
        }
    }
}
```

Page and Database Objects API
-----------------------------

[](#page-and-database-objects-api)

The `MdNotion` package provides rich object models for working with Notion pages and databases. Both `Page` and `Database` objects extend `BaseObject` and use several traits to provide comprehensive functionality.

### Page Object API

[](#page-object-api)

#### Basic Properties and Methods

[](#basic-properties-and-methods)

```
use Redberry\MdNotion\Objects\Page;

// Create from data
$page = Page::from([
    'id' => 'page-id-123',
    'title' => 'My Page Title',
    'content' => '# Page content...',
    'has_children' => true
]);

// Core properties
$page->getId();                    // string - Page ID
$page->getTitle();                 // string - Page title
$page->getContent();               // ?string - Page content
$page->hasContent();               // bool - Whether page has content
$page->hasChildren();              // bool - Whether page has child pages
```

#### Content Management

[](#content-management)

```
// Content operations
$page->setContent('# New content');
$page->getContent();               // Returns MD string: '# New content'
$page->hasContent();               // Returns: true

// Child database
$databases = $page->getChildDatabases();  // Collection
```

#### Fetching and Updating

[](#fetching-and-updating)

When page is accessed as child page of another, it may not contain all information you need, including child pages, markdown content and etc. To get the needed data, you can use ID with MdNotion again or use `fetch` method on page instance.

```
// Fetch latest data from Notion API
$page->fetch();         // Updates current instance with fresh data

// The fetch method preserves object identity
$originalPage = Page::from(['id' => 'page-123']);
$updatedPage = $originalPage->fetch();
// $originalPage === $updatedPage (same page, updated data)
```

### Database Object API

[](#database-object-api)

#### Basic Properties and Methods

[](#basic-properties-and-methods-1)

```
use Redberry\MdNotion\Objects\Database;

// Create from data
$database = Database::from([
    'id' => 'db-id-123',
    'title' => 'My Database',
    'tableContent' => '| Name | Status |\n|------|--------|\n| Task 1 | Done |'
]);

// Core properties
$database->getId();                // string - Database ID
$database->getTitle();             // string - Database title
$database->getTableContent();      // ?string - Database as markdown table
$database->hasTableContent();      // bool - Whether has table content
```

#### Table Content Management

[](#table-content-management)

```
// Table content operations
$database->getTableContent();      // Returns markdown table string
$database->hasTableContent();      // Returns true if table content exists

// Read items content (populate child pages with content)
$database->readItemsContent();
```

#### Fetching and Updating

[](#fetching-and-updating-1)

```
// Fetch latest data from Notion API
$database->fetch(); // Updates current instance with fresh data
```

### Shared API (from BaseObject &amp; Traits)

[](#shared-api-from-baseobject--traits)

Both `Page` and `Database` objects share these APIs through inheritance and traits:

#### Title Management (HasTitle trait)

[](#title-management-hastitle-trait)

```
// Title operations
$object->getTitle();               // string - Get title
$object->setTitle('New Title');    // Set title
$object->renderTitle(1);           // string - Render as markdown heading (# Title)
$object->renderTitle(2);           // string - Render as level 2 heading (## Title)
$object->renderTitle(3);           // string - Render as level 3 heading (### Title)
```

#### Icon Management (HasIcon trait)

[](#icon-management-hasicon-trait)

```
// Icon operations
$object->getIcon();                // ?array - Get icon data
$object->setIcon($iconData);       // Set icon data
$object->hasIcon();                // bool - Whether has icon
$object->processIcon();            // string - Get icon as emoji/markdown

// Icon types supported:
// - Emoji: Returns emoji character
// - External: Returns [IconName](url) markdown link
// - File: Returns [🔗](url) markdown link
```

#### Metadata Management (HasMeta trait)

[](#metadata-management-hasmeta-trait)

```
// Timestamps
$object->getCreatedTime();         // string - ISO timestamp
$object->getLastEditedTime();      // string - ISO timestamp

// User information
$object->getCreatedBy();           // array - User data who created
$object->getLastEditedBy();        // array - User data who last edited

// Status flags
$object->isArchived();             // bool - Whether archived
$object->isTrashed();              // bool - Alias for isInTrash()
```

#### Parent Relationship (HasParent trait)

[](#parent-relationship-hasparent-trait)

```
// Parent operations
$object->getParent();              // array - Full parent data
$object->hasParent();              // bool - Whether has parent
$object->getParentType();          // ?string - Parent type (page_id, workspace, etc.)
$object->getParentId();            // ?string - Parent ID based on type
```

#### Child Pages Management (HasChildPages trait)

[](#child-pages-management-haschildpages-trait)

```
// Child pages operations
$object->getChildPages();          // Collection - Child pages collection
$object->hasChildPages();          // bool - Whether has child pages
```

#### URLs and Properties

[](#urls-and-properties)

```
// URL management
$object->getUrl();                 // ?string - Notion URL
$object->hasUrl();                 // bool - Whether has URL
$object->getPublicUrl();           // ?string - Public sharing URL
$object->hasPublicUrl();           // bool - Whether has public URL

// Properties management
$object->getProperties();          // array - All Notion properties
$object->hasProperties();          // bool - Whether has properties
$object->getProperty('title');     // mixed - Get specific property
```

#### Serialization and Data Conversion

[](#serialization-and-data-conversion)

```
// Convert to array
$data = $object->toArray();        // Complete object data as array

// Fill from array (merge new data)
$object->fill($newData);           // Updates object with new data, preserves existing

// Static creation
$object = Page::from($data);       // Create new instance from data array
$object = Database::from($data);   // Create new instance from data array
```

### Advanced Usage Examples

[](#advanced-usage-examples)

#### Working with Object Identity

[](#working-with-object-identity)

```
// Objects maintain identity during fetch operations
$page = Page::from(['id' => 'page-123']);
$samePageReference = $page->fetch();

// $page === $samePageReference (same object instance)
// But $page now has updated content from Notion API
```

#### Child Content Management

[](#child-content-management)

```
// Page with child databases
$childDbs = $page->getChildDatabases();

// Database with child pages
$childPages = $database->getChildPages();

// Read all child page content
$database->readItemsContent();
```

#### Partial Data Updates

[](#partial-data-updates)

```
$page = Page::from([
    'id' => 'page-123',
    'title' => 'Original Title',
    'content' => 'Original content'
]);

// Partial update - only updates specified fields
$page->fill([
    'title' => 'Updated Title'
    // content remains "Original content"
]);

echo $page->getTitle();   // "Updated Title"
echo $page->getContent(); // "Original content" (preserved)
```

Customization
-------------

[](#customization)

### Custom Blade Templates

[](#custom-blade-templates)

You can customize how markdown is rendered by creating your own Blade templates:

#### Create your custom templates

[](#create-your-custom-templates)

To change the layout or logic how `read` or `full` methods render the markdown, you should create a new view and replace them in config:

```
// config/md-notion.php
return [
    'templates' => [
        'page_markdown' => 'custom.page-template',     // For content()->read()
        'full_markdown' => 'custom.full-template',     // For full()
    ],
];
```

When customizing templates, you have access to these variables:

##### For page templates (`content()->read()`):

[](#for-page-templates-content-read)

- `$current_page` - Array with `title`, `content`, `hasContent`
- `$child_databases` - Array of database data with `title`, `table_content`, `hasTableContent`
- `$child_pages` - Array of page data with `title`, `content`, `hasContent`
- `$withDatabases` - Boolean flag
- `$withPages` - Boolean flag
- `$hasChildDatabases` - Boolean flag
- `$hasChildPages` - Boolean flag

##### For full templates (`full()`):

[](#for-full-templates-full)

- Complete recursive data structure with nested `current_page`, `child_databases`, `child_pages`
- Each level includes `hasChildDatabases`, `hasChildPages`, `level` for depth tracking

You can check current blade templates here:

- `read()` Method: **resources\\views\\page-md.blade.php**
- `full()` Method: **resources\\views\\full-md.blade.php**

### Custom Block Adapters

[](#custom-block-adapters)

Create custom adapters to handle specific Notion block types:

#### 1. Create a custom adapter:

[](#1-create-a-custom-adapter)

You will need adapter class extending `src\Adapters\BaseBlockAdapter.php` and custom blade template rendering the data.

```
// app/Adapters/CustomCodeAdapter.php
