Building a Notion PowerShell Module: Part 3

Published:10 November 2023 - 4 min. read

Azure Cloud Labs: these FREE, on‑demand Azure Cloud Labs will get you into a real‑world environment and account, walking you through step‑by‑step how to best protect, secure, and recover Azure data.

Building on the previous two Notion tutorials, you have learned how to create a Notion integration token and retrieve, update, and delete Notion blocks via PowerShell and the Notion API.

Even more powerful are Notion’s Databases. Relational databases help to organize data logically and provide opportunities for analysis. With Notion, store pages within Databases with additional properties. In this tutorial, learn how to create a page, database, and database item. Build upon the Notion PowerShell module to continue expanding functionality.

Prerequisites

To follow along in this tutorial, you only need a Notion account and PowerShell; here, PowerShell v7.3.7 is in use.

The Power of Notion Databases

Though you may think of pages as standalone, within Notion, they are also integral to Databases. Therefore, combining the Page operations with those of Databases helps to understand the linkage. In Notion, Databases may have properties of many different types, such as Selects, Checkboxes, or Numbers.

Creating a Page

First, you need a place to put the database. To demonstrate this, create a new page using the /pages endpoint. The resulting id will be the GUID used as the parent_id of the new Database.

$APIKey     = 'secret_sK5Qir0UAgkLF1iauP5iMsq1vq3YW5S9o5GvF7M8PjK'
$APIURI     = 'https://api.notion.com/v1'
$APIVersion = '2022-06-28'
$GUID       = 'a2b3646d-e941-4df4-874d-56153139b618'

$Params = @{
    "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
    }
    "Method"  = 'POST'
    "URI"     = ("{0}/pages" -F $APIURI, [GUID]::new($GUID))
    "Body"    = @{
        "parent" = @{
            "type"    = 'page_id'
            "page_id" = $GUID
        }
        "properties" = @{
            "title" = @(
                @{
                    "text" = @{"content" = "Test" }
                }
            )
        }
        "children" = @()
    } | ConvertTo-JSON -Depth 100
}

$Result = Invoke-RestMethod @Params
Result of creating a new page.
Result of creating a new page.

Creating the Database

With the page newly created, you will use the page_id parent with the /databases endpoint to create a new Database with a single required Title property. There is a required title property that gives the Database its unique name as well.

$Params = @{
    "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
    }
    "Method"  = 'POST'
    "URI"     = ("{0}/databases" -F $APIURI)
    "Body"    = @{
        "parent" = @{
            "type"    = "page_id"
            "page_id" = $Result.id
        }
        "title" = @(
            @{
                "type" = "text"
                "text" = @{
                  "content" = "Test"
                  "link" = $null
                }
            }
        )
        "properties" = @{
            "Name" = @{
                "type" = "title"
                "title" = @{}
            }
						"Checkbox" = @{
                "type" = "checkbox"
                "checkbox" = @{}
            }
            "Number" = @{
                "type" = "number"
                "number" = @{
                  "format" = "number_with_commas"
                }
            }
        }
    } | ConvertTo-Json -Depth 100
}

$Result = Invoke-RestMethod @Params
Result of creating a new database.
Result of creating a new database.

Creating a Page in the Database

Finally, to store data within the Database, you must create a new item: a full Notion page. As the page item exists in the database, it has the added benefit of any additional properties. In the example above, you created a checkbox and number property.

$Params = @{
    "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
    }
    "Method"  = 'POST'
    "URI"     = ("{0}/pages" -F $APIURI)
    "Body"    = @{
        "parent" = @{
            "type"        = "database_id"
            "database_id" = $Result.id
        }
        "properties" = @{
            "Name" = @{
                "title" = @(
                    @{
                        "text" = @{"content" = "This is a Test Task"}
                    }
                )
             }
             "Checkbox" = @{
                "checkbox" = $True
             }
             "Number" = @{
                "number" = 1000
             }
        }
    } | ConvertTo-Json -Depth 100
}

$Result = Invoke-RestMethod @Params
Result of creating a new database item.
Result of creating a new database item.

Adding to the Module

To round out the module, three new functions will be created. These functions will be:

  • New-NotionPage
  • New-NotionDatabase

You may wonder why there isn’t a New-NotionDatabase. As technically you are calling the same endpoint, /pages, for both Databases and Pages, you can extend your New-NotionPage function to cover both use cases.

Introduced in this function is the concept of parameter sets. Parameter sets offer a way to separate parameters into logical groupings and only allow those that belong together. Defined below is the DefaultParameterSetName within the CmdletBinding declaration. This gives a default for the function.

It’s necessary to define all the parameter sets each parameter belongs in. If a parameter must be in both, then multiple declarations must be used.

New-NotionPage

Function New-NotionPage {
    [CmdletBinding(SupportsShouldProcess = $True, DefaultParameterSetName = 'Page')]
  
    Param(
        [String]$APIKey,
        [String]$APIVersion,
        [ValidateScript( { [System.URI]::IsWellFormedUriString( $_ ,[System.UriKind]::Absolute ) } )][String]$APIURI,

        [Parameter(ValueFromPipelineByPropertyName = $True)]
        [Alias("ID")]
        [ValidateScript( { Try { If ( [GUID]::Parse( $_ ) ) { $True } } Catch { $False } } )][String]$GUID,

        [Parameter(Mandatory, ParameterSetName = 'Page')]
        $Title,

        $Content,

        [Parameter(ParameterSetName = 'Database')]
        $Properties,

        [Parameter(ParameterSetName = 'Database')]
        [Switch]$Database
    )
  
    Process {
        $Body = @{
            "parent"     = $Null
            "properties" = $Null
        }

        If ($Database) {
            $Body.properties = $Properties
            $Body.parent = @{
                "type"    = 'database_id'
                "database_id" = [GUID]::New($GUID)
            }
        } Else {
            $Body.parent = @{
                "type"    = 'page_id'
                "page_id" = [GUID]::New($GUID)
            }
            $Body.properties = @{
                "title" = @(
                    @{
                        "text" = @{ "content" = $Title }
                    }
                )
            }          
        }

        If ($Content) {
            $Body.Add("children", $content)
        }

        Write-Verbose ($Body | Out-String)

        $Params = @{
            "Headers" = @{
                "Authorization"  = "Bearer {0}" -F $APIKey
                "Content-type"   = "application/json"
                "Notion-Version" = "{0}" -F $APIVersion
            }
            "Method" = 'POST'
            "URI"    = ("{0}/pages" -F $APIURI)
            "Body"   = $Body | ConvertTo-JSON -Depth 100
        }

        Write-Verbose "[Process] Params: $($Params | Out-String)"

        If ($PSCmdlet.ShouldProcess($GUID,"Adding Page")) {
            Try {
                $Result = Invoke-RestMethod @Params -ErrorAction 'Stop'
            } Catch {
                $Message = ($Error[0].ErrorDetails.Message | ConvertFrom-JSON).message

                Write-Error "Command Failed to Run: $Message"
            }

            If ($Result) {
                $Result
            }
        }
    }
}

New-NotionDatabase

Function New-NotionDatabase {
    [CmdletBinding(SupportsShouldProcess = $True)]
  
    Param(
      [String]$APIKey,
      [String]$APIVersion,
      [ValidateScript( { [System.URI]::IsWellFormedUriString( $_ ,[System.UriKind]::Absolute ) } )][String]$APIURI,
  
      [Parameter(ValueFromPipelineByPropertyName = $True)]
      [Alias("ID")]
      [ValidateScript( { Try { If ( [GUID]::Parse( $_ ) ) { $True } } Catch { $False } } )][String]$GUID,

      [Parameter(Mandatory)]
      $Title,
  
      $Properties
    )
  
    Process {
        $Params = @{
            "Headers" = @{
                "Authorization"  = "Bearer {0}" -F $APIKey
                "Content-type"   = "application/json"
                "Notion-Version" = "{0}" -F $APIVersion
            }
            "Method" = 'POST'
            "URI"    = ("{0}/databases" -F $APIURI)
            "Body"   = @{
                "parent" = @{
                    "type"    = 'page_id'
                    "page_id" = [GUID]::New($GUID)
                }
                "title" = @(
                    @{
                        "type" = "text"
                        "text" = @{
                          "content" = $Title
                          "link" = $Null
                        }
                    }
                )
                "properties" = $Properties
            } | ConvertTo-JSON -Depth 100
        }

        Write-Verbose "[Process] Params: $($Params | Out-String)"

        If ($PSCmdlet.ShouldProcess($GUID,"Adding Database")) {
            Try {
                $Result = Invoke-RestMethod @Params -ErrorAction 'Stop'
            } Catch {
                $Message = ($Error[0].ErrorDetails.Message | ConvertFrom-JSON).message

                Write-Error "Command Failed to Run: $Message"
            }

            If ($Result) {
                $Result
            }
        }
    }
}

When run together, you can see the results are the same as if you were to run the individual API calls, but in a more user-friendly nomenclature. By passing in the resulting objects from the previous calls you can simplify creation of child pages and objects.

$Parent = 'a2b3646de9414df4874d56153139b618'

$Result = New-NotionPage -GUID $Parent -Title "Database Page"

$Database = New-NotionDatabase -GUID $Result.id -Title "Test Database" -Properties @{
    "Name" = @{
        "type" = "title"
        "title" = @{}
    }
    "Checkbox" = @{
        "type" = "checkbox"
        "checkbox" = @{}
    }
    "Number" = @{
        "type" = "number"
        "number" = @{
          "format" = "number_with_commas"
        }
    }
}

$Item = New-NotionPage -Database -GUID $Database.id -Properties @{
    "Name" = @{
        "title" = @(
            @{
                "text" = @{"content" = "This is a Test Task"}
            }
        )
     }
     "Checkbox" = @{
        "checkbox" = $True
     }
     "Number" = @{
        "number" = 1000
     }
}
New containing page.
New containing page.
The full-page database.
The full-page database.
Resulting database with subsequently added item.
Resulting database with subsequently added item.

Next Steps

The next step is to add a few supporting functions to the module to create stronger typing of available block types and additional usability. The following few articles will wrap up the function and finish creating a usable and full-featured module!

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!