Skip to main content



You will deal with Page when handling database query results, retrieving a page or as a result of a page you just created or updated.

A Page contains the metadata of a Notion page. We tend to follow the Notion api, and they don't provide the content of the page. Instead, you have to deal with block instead.

For more information, you can check the Notion documentation.

Retrieve a page

The easiest way to retrieve a page is:

val page: ZIO[Notion, NotionError, Page] = Notion.retrievePage("page-id")

No exhaustively, the page will contain its title, its properties and when it is created.

For more information, you can check the notion documentation.

Deal with properties

It can be troublesome to deal with page's properties. Indeed, the properties is a map composed by properties that can theoretically be of any kind.

As an example, if you want to retrieve a number you will first have to :

  • ensure that the property exist in the database
  • ensure that the property is indeed a number property
  • ensure that the property is fulfilled with data

You will easily have to write something like this:

val maybeProperty: Option[Property] ="number") {
case Some(property) =>
property match {
case number: Property.Number => number.number match {
case Some(value) => // 🎉🎉🎉
case None => // the property "name" exists, it is a number but the row has no data in it
case _ => // the property "name" exists but it is not a number
case None => // the property "name" does not exist

That's why you can use the page function propertiesAs[A] to convert your properties into a defined case class. Under the hood, it uses Magnolia to automatically derive the case class.

If we take the same example, you can now write something like this:

case class Properties(number: Double)

val propertiesOrError = page.propertiesAs[Properties]

It will return a Validation[ParsingError, A], this data structure is provided by zio-prelude.

This way of dealing with properties has several interesting features:

  • You can deal with optional value
  • You can deal with list of values (Multiselect, People, etc.)
  • You can create encoder for your own enumeration (Select)

Using your own non-primitive types

If you need your case class to use non-primitive types you can add an implicit PropertyConverter such as the following:

import zio.notion.Converter.required

final case class Properties(url: Url)
final case class Url(uri: String)

implicit val urlPropertyConverter: PropertyConverter[Url] = {
case Property.Files(_, files) =>
required(files.headOption).flatMap {
case Link.File(_, file) => Validation.succeed(Url(file.url))
case Link.External(_, external) => Validation.succeed(Url(external.url))
case _ =>"Url"))

val propertiesOrError = page.propertiesAs[Properties]

Using a different name from Notion

The automatic derivation works by using the case class parameter name as the Notion property name. However, sometimes it is not appropriate.

Imagine the following table:

NamePrice (in $)Date of the shipping
purchase 11$02/02/2022
purchase 240$01/02/2022
purchase 320$03/02/2022

For each page, if you want to retrieve the properties using automatic derivation, you will have to write something like :

import java.time.LocalDate

final case class Properties(
Name: String,
`Price (in $)`: Double,
`Date of the shipping`: LocalDate

Let's be honest, it is not practical. You still can enforce camelCase convention to your Notion users but is not convenient too since Notion is meant to be human-readable.

Luckily, you can annotate your parameters :

import java.time.LocalDate

final case class Properties(
@NotionColumn("Name") name: String,
@NotionColumn("Price (in $)") price: Double,
@NotionColumn("Date of the shipping") shippingDate: LocalDate

Update a page

Notion allows you to update:

  • The properties content of the page (including its title)
  • The icon of the page
  • The cover of the page
  • Whether you want to archive the page or not

For more information, you can check the notion documentation.

Strictly speaking, you have to provide a list of operations describing the list of changes from the current page to the expected one.

There is two types of operations :

  • Stateless operations are operations that does not require the current state of the page to generate a patch. As an example, removeIcon is a stateless operation because we don't need to explicitly know if the page already has an icon or not.
  • Stateful operations are operations that does require the current state of the page. The only current stateful operation is UpdateProperty. It indeed requires the current page property to update it.

We explicitly differentiate the operations because stateless operations does not require a page to work. It means that we don't have to retrieve the page first to update it. That is why the Notion interface provides several update methods :

def updatePage(pageId: String, operations: StatelessOperations): IO[NotionError, Page]
def updatePage(page: Page, operations: Operations): IO[NotionError, Page]
def updatePage(pageId: String, operation: Operation.Stateless): IO[NotionError, Page]
def updatePage(page: Page, operation: Operation): IO[NotionError, Page]

We provide several kind of operations that can compose multiple operations:

import zio.notion.dsl._ // We advise you to import the dsl

val operations = $"col1".asCheckbox.patch.check ++ removeIcon

Here is a non-exhaustive list of operation:

val operation = archive                        // Archive the page (Stateless)
val operation = unarchive // Unarchive the page (Stateless)
val operation = removeIcon // Remove the current icon of the page (Stateless)
val operation = removeCover // Remove the current cover of the page (Stateless)
val operation = setIcon(newIcon) // Set a new icon to the page (Stateless)
val operation = setCover(newCover) // Set a new cover to the page (Stateless)
val operation = removeProperty("name") // Remove the property "name" of the page (Stateless)
val operation = $"col1".asCheckbox.patch.check // Check the col1 checkbox property (Stateless)
val operation = $"col1".asNumber.patch.ceil // Apply a transformation to the col1 number property (Stateful)

We advise you to check autocompletion for $"col1".as, we provide operations for all notion types.

You also can create your own, as an example, if I want to multiply a number by itself:

val operation = $"col1".asNumber.patch.update(n => n * n) // It is an update (Stateful)

Create a page

You can also create a new page by providing the parent page in both a page and a database.