notion-client: Type-safe Haskell client for the Notion API

[ library, mit, program, web ] [ Propose Tags ] [ Report a vulnerability ]

This package provides comprehensive and type-safe bindings to the Notion API, providing both a Servant interface and non-Servant interface for convenience. . Read the README below for a fully worked usage example. . Otherwise, browse the Notion.V1 module, which is the intended package entrypoint.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.2.0.0, 0.3.0.0, 0.3.1.0, 0.5.0.0, 0.6.0.0, 0.6.1.0, 0.7.0.0, 0.7.0.1, 0.7.0.2
Change log CHANGELOG.md
Dependencies aeson (>=2.2 && <2.3), base (>=4.15.0.0 && <5), base16-bytestring (>=1.0 && <1.1), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.8), cryptohash-sha256 (>=0.11 && <0.12), filepath (>=1.4 && <1.6), http-api-data (>=0.6 && <0.7), http-client-tls (>=0.3 && <0.4), notion-client, scientific (>=0.3 && <0.4), servant (>=0.20 && <0.21), servant-client (>=0.20 && <0.21), servant-multipart-api (>=0.12 && <0.13), servant-multipart-client (>=0.12 && <0.13), text (>=2.0 && <2.2), time (>=1.11 && <1.15), time-compat (>=1.9 && <1.10), unordered-containers (>=0.2 && <0.3), vector (>=0.13 && <0.14) [details]
Tested with ghc ==9.12.2
License MIT
Author Nadeem Bitar
Maintainer nadeem@gmail.com
Uploaded by shinzui at 2026-06-27T14:35:20Z
Category Web
Home page https://github.com/shinzui/notion-client
Bug tracker https://github.com/shinzui/notion-client/issues
Source repo head: git clone https://github.com/shinzui/notion-client.git
Distributions
Executables notion-client-example
Downloads 104 total (28 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for notion-client-0.7.0.2

[back to package description]

Notion API Client for Haskell

A type-safe Haskell client for the Notion API (version 2026-03-11).

Features

  • Type-safe API bindings using Servant
  • Comprehensive coverage of Notion API endpoints
  • Support for all Notion object types: Pages, Databases, Data Sources, Blocks, Users, etc.
  • Simple client interface with sensible defaults

Installation

Add to your package.yaml or .cabal file:

dependencies:
  - notion-client

Usage

Here's a simple example of retrieving a Notion page:

module Main where

import Notion.V1
import Notion.V1.Pages
import Data.Text qualified as Text
import System.Environment qualified as Environment

main :: IO ()
main = do
    token <- Environment.getEnv "NOTION_TOKEN"

    clientEnv <- getClientEnv "https://api.notion.com/v1"

    let Methods{ retrievePage } = makeMethods clientEnv (Text.pack token)

    page <- retrievePage "page-id-here"

    print page

Creating a page with typed properties

import Notion.V1
import Notion.V1.Common (Parent(..))
import Notion.V1.Pages
import Notion.V1.PropertyValue qualified as PV
import Data.Map qualified as Map
import Data.Vector qualified as Vector

createNewPage :: Methods -> IO PageObject
createNewPage Methods{createPage} = do
    let pageProperties = Map.fromList
            [ ("title", PV.titleValue (Vector.singleton titleRichText))
            , ("Status", PV.selectValue "In Progress")
            , ("Priority", PV.selectValue "High")
            , ("Due", PV.dateValue "2024-06-01" Nothing)
            , ("Score", PV.numberValue 42)
            , ("Done", PV.checkboxValue False)
            ]

        newPage = mkCreatePage
            (DataSourceParent { dataSourceId = "data-source-id-here" })
            pageProperties

    createPage newPage

Reading typed properties

import Notion.V1.PropertyValue

readPageStatus :: PageObject -> Maybe Text
readPageStatus page =
    case Map.lookup "Status" (properties page) of
        Just (SelectValue _ (Just opt)) -> Just (name opt)
        _ -> Nothing

Creating a page with markdown

import Notion.V1
import Notion.V1.Common (Parent(..))
import Notion.V1.Pages

createMarkdownPage :: Methods -> IO PageObject
createMarkdownPage Methods{createPage} = do
    let newPage = (mkCreatePage
            (DataSourceParent { dataSourceId = "data-source-id" })
            mempty)
            { markdown = Just "# Hello\n\nThis page was created with **markdown**." }

    createPage newPage

Editing page content with markdown

import Notion.V1
import Notion.V1.Pages

editPage :: Methods -> PageID -> IO PageMarkdown
editPage Methods{updatePageMarkdown} pageId =
    updatePageMarkdown pageId $
        UpdateContent UpdateContentRequest
            { contentUpdates = fromList
                [ ContentUpdate
                    { oldStr = "old text"
                    , newStr = "new text"
                    , replaceAllMatches = Nothing
                    }
                ]
            , allowDeletingContent = Nothing
            }

Error handling

import Control.Exception (catch)
import Notion.V1.Error (NotionError(..))

safeRetrieve :: Methods -> PageID -> IO ()
safeRetrieve Methods{retrievePage} pageId =
    retrievePage pageId `catch` \(e :: NotionError) ->
        putStrLn $ "Notion error: " <> code e <> " - " <> message e

Auto-pagination

import Notion.V1.Pagination (paginateAll)
import Notion.V1.DataSources (QueryDataSource(..))

allPages <- paginateAll $ \cursor ->
    queryDataSource methods dsId QueryDataSource
        { filter = Nothing, sorts = Nothing
        , startCursor = cursor, pageSize = Just 100
        , inTrash = Nothing, filterProperties = Nothing
        }

Usage with effectful

Callers that use the effectful effect system can opt into an Eff-typed surface via the companion package notion-client-effectful. Every Notion.V1.Methods field is re-exposed as a smart constructor of a Notion effect, and a runNotion interpreter dispatches through a concrete Methods value. NotionError responses surface via the Error NotionError effect rather than as IO exceptions.

module Demo where

import Data.Text (Text)
import Data.Text qualified as Text
import Effectful (Eff, runEff, (:>))
import Effectful.Error.Static (Error, runErrorNoCallStack)
import Notion.V1                    (getClientEnv, makeMethods)
import Notion.V1.Common             (UUID (..))
import Notion.V1.Effectful qualified as NE
import Notion.V1.Error              (NotionError)
import System.Environment qualified as Env

demo :: (NE.Notion :> es, Error NotionError :> es) => UUID -> Eff es Text
demo pid = do
  page <- NE.retrievePage pid
  pure (Text.pack (show page))

main :: IO ()
main = do
  token <- Text.pack <$> Env.getEnv "NOTION_TOKEN"
  env <- getClientEnv "https://api.notion.com/v1"
  let methods = makeMethods env token
  result <-
    runEff . runErrorNoCallStack @NotionError . NE.runNotion methods $
      demo (UUID "00000000-0000-0000-0000-000000000000")
  print result

See notion-client-effectful/README.md for the full import pattern (qualifying one of the two Notion.V1 modules is required — every smart constructor shares its name with the matching Methods record selector).

API Coverage

  • Databases: Create, retrieve, update, and query databases
  • Data Sources: Create, retrieve, update, query data sources; list templates
  • Pages: Create (with blocks or markdown), retrieve, update, move pages; retrieve and update page markdown
  • Blocks: Retrieve, update, append children (with position control), and delete blocks
  • Users: List, retrieve users and bot users
  • Views: Create, retrieve, update, delete, list, and query database views (all 10 view types)
  • Search: Search for pages and data sources
  • Comments: Create and list comments
  • Custom Emojis: List workspace custom emojis
  • Webhooks: Event types (including view events) and signature verification

Running the Example

The repository includes a comprehensive example in the notion-client-example directory that demonstrates how to use most API endpoints.

To run the example:

# Set required environment variables
export NOTION_TOKEN="your-integration-token"

# Optional: Set these if you want to test specific database/page endpoints
export NOTION_TEST_DATABASE_ID="your-database-id"
export NOTION_TEST_PAGE_ID="your-page-id"

# Run the example
cabal run notion-client-example

Obtaining API Credentials

  1. Create a Notion integration at https://www.notion.so/my-integrations
  2. Get your integration token from the integration settings
  3. Share any Notion pages or databases you want to access with your integration
    • Open the page/database in Notion
    • Click "Share" in the top right
    • Enter your integration name and click "Invite"
  4. Get the page/database IDs from their URLs:
    • Page URL: https://www.notion.so/Your-Page-Title-83715d7c1111424aaa11d7fc1111bd2a
    • Page ID: 83715d7c1111424aaa11d7fc1111bd2a (the last part of the URL)

License

MIT