This tutorial will teach you how to create an advanced function to interact with the Notion API. Notion is a powerful web application to manage knowledge in flexible workspaces. Take your automation to the next level with Notion PowerShell integration!
Prerequisites
To follow along in this tutorial, you only need a Notion account and PowerShell; here, PowerShell v7.3.7 is in use.
Creating a Notion Integration Token
Once signed into Notion, open a browser to the “My integrations” page. Here, you will create a new integration. The resulting secret key will be used in every request, authenticating your REST API calls.
1. Once on the My Integrations page, click the “Create new integration” placeholder or “+ New integration” button.
2. Next, fill the details out accordingly. Choose the Associated Workspace and give a Name to identify the integration. Optionally, upload a Logo to differentiate your integration further.
3. Click the eye icon to show the integration secret. Copy this integration secret for later use in the PowserShell script.
4. Navigate to the Capabilities section and make any necessary changes, here, the defaults are used.
Interacting with the Notion API in PowerShell
With the integration created and the secret key in hand, navigate to the given page you will interact with. You may give an integration access to the top-level, or root, of a series of pages. When granted access to the root of a series of pages, all child pages will also be accessible by the integration.
In this example, a PowerShell Testing page has been created. You can add the integrations access through the Connections menu by clicking on the three dots in the upper-right corner to access additional menu options. Highlight the “Add connections” and choose your newly created integration named “PowerShell.”
A confirmation will show, and as noted, will ask if you grant the integration access to all child pages as well.
Retrieving the Pages Blocks
Everything in Notion is based on the idea of blocks. A paragraph is considered a block, even an empty one. Since this page has no content, you can request the content, and the default empty paragraph block will be returned.
You will need several pieces of information to create a REST API call to Notion.
- API Key – The previously created integration and saved API key.
- API URI – The URL of the REST API.
- API Version – Breaking changes can, and do, occur from time to time in the REST API. You need to give the API version you are operating against. The current version is
2022-06-28
at the time of this article’s creation. - Page Size – You can only request so many blocks in a single REST API call, and the maximum for the child blocks call is
100
. - GUID (Globally Unique Identifier) – Each Notion page is identified by its GUID, the value appended after friendly text. You can copy and paste that value from the URL. For example, the page is
PowerShell-Testing-a2b3646de9414df4874d56153139b618
, but the GUID isa2b3646de9414df4874d56153139b618
.
💡 Although the secret key is shown, it has since been removed and is no longer valid.
With all those pieces in hand, it’s time to create your REST API call. Like most REST API calls in PowerShell, you will call the Invoke-RestMethod
cmdlet. The parameters are laid out using parameter splatting for readability, and the request results are stored in the $Result
variable. To see the results, the results
property is requested at the end.
$APIKey = 'secret_fS2A21NwTL4L6XWFTTdgmF3xboWiYXExKZ8Pw15oRMw'
$APIURI = 'https://api.notion.com/v1'
$APIVersion = '2022-06-28'
$PageSize = '100'
$GUID = 'a2b3646de9414df4874d56153139b618'
$Params = @{
"Headers" = @{
"Authorization" = "Bearer {0}" -F $APIKey
"Content-type" = "application/json"
"Notion-Version" = "{0}" -F $APIVersion
}
"Method" = 'GET'
"URI" = ("{0}/blocks/{1}/children?page_size={2}" -F $APIURI, [GUID]::new($GUID), $PageSize)
}
$Result = Invoke-RestMethod @Params
$Result.results
As you can see, a single block was returned with detailed information. The actual content, though blank, is within the paragraph
property and the rich_text
property. Right now, there is nothing contained within.
If the text “Hello from Notion!” is entered in the test page, and the code is rerun, then the returned rich_text
value will reflect that.
Creating an Advanced PowerShell Function
With the basics in hand, what are the next steps? To make a more robust reusable function, you can leverage the tenets of advanced PowerShell function. The improvements are less code to run on the command line, easier script use, and more validation. In addition, with the page_size
limit of 100
, a page over that will need a recursive call to retrieve all of the results, which this advanced function can do.
With the intent in mind, how does the code work? For any advanced function, you must include the [CmdletBinding()]
underneath the function declaration. This indicates to PowerShell that you may use the advanced features.
Next, the Param
code block is declared. For two parameters, $APIURI
, and $GUID
, advanced validation is done via the ValidateScript
decorator. Using the [System.URI]::IsWellFormedUriString
method, this validates that the passed value is an accurate URI. The [GUID]::Parse
method on the [GUID]
type accelerator tests if the passed-in value is a true GUID.
In the Begin block, instead of redefining the parameters passed to Invoke-RestMethod
on every iteration, they are defined once at the start of the pipeline. The tricker parts are in the Process
block. The start_cursor
parameter for Notion defines where the start of the result gathering begins. If defined, the previous values will be skipped, and the following 100
blocks will be counted from there.
In this function, that is only defined on a recursive call. For example, Notion will return a true or false $Results.has_more
value if more than 100
blocks exist. If this is true, call the same Get-NotionBlock
function, pass in the same parameters via the $PSBoundParameters
special variable, and give the $Result.next_cursor
value to the -StartCursor
parameter. This will continue to do so until has_more
is false.
Similarly, this is how the has_children
property works, as child blocks may need to be retrieved as well. The function will call itself to retrieve those before continuing.
Function Get-NotionBlock {
[CmdletBinding()]
Param(
[String]$APIKey,
[String]$APIVersion,
[ValidateScript( { [System.URI]::IsWellFormedUriString( $_ ,[System.UriKind]::Absolute ) } )][String]$APIURI,
[ValidateScript( { Try { If ( [GUID]::Parse( $_ ) ) { $True } } Catch { $False } } )][String]$GUID,
[String]$StartCursor,
[Int]$PageSize = 100
)
Begin {
$Params = @{
"Headers" = @{
"Authorization" = "Bearer {0}" -F $APIKey
"Content-type" = "application/json"
"Notion-Version" = "{0}" -F $APIVersion
}
"Method" = 'GET'
}
}
Process {
Try {
If ($StartCursor) {
$Params.Add("URI" , ("{0}/blocks/{1}/children?start_cursor={3}&page_size={2}" -F $APIURI, [GUID]::new($GUID), $PageSize, $StartCursor))
} Else {
$Params.Add("URI" , ("{0}/blocks/{1}/children?page_size={2}" -F $APIURI, [GUID]::new($GUID), $PageSize))
}
Write-Verbose "[Process] Params: $($Params | Out-String)"
$Result = Invoke-RestMethod @Params
If ($Result.has_more) {
$Result.results
If ([Bool]($Result.results | Where-Object has_children -EQ $True)) {
$Result.results | Where-Object has_children -EQ $True | ForEach-Object { Get-NotionBlock @PSBoundParameters -StartCursor:$Null -GUID $PSItem.id }
}
Write-Verbose "[Process] More Results Exist"
Get-NotionBlock @PSBoundParameters -StartCursor $Result.next_cursor
} Else {
$Result.results
If ([Bool]($Result.results | Where-Object has_children -EQ $True)) {
$Result.results | Where-Object has_children -EQ $True | ForEach-Object { Get-NotionBlock @PSBoundParameters -StartCursor:$Null -GUID $PSItem.id }
}
}
} Catch {
$Message = ($Error[0].ErrorDetails.Message | ConvertFrom-JSON).message
Write-Error "Command Failed to Run: $Message"
}
}
}
With the advanced function defined, it’s time to call the function and retrieve the blocks! Create the $Params
variable and pass that splatted variable into the Get-NotionBlock
function. Here, only the first object is shown.
$Params = @{
"APIKey" = 'secret_fS2A21NwTL4L6XWFTTdgmF3xboWiYXExKZ8Pw15oRMw'
"APIVersion" = '2022-06-28'
"APIURI" = 'https://api.notion.com/v1'
"GUID" = 'a2b3646de9414df4874d56153139b618'
}
Get-NotionBlock @Params | Select-Object -First 1
What if the page has over 100
blocks? When that happens, the function runs recursively. By passing in the -Verbose
parameter, you can see how the start_cursor
changes. To keep the results concise, pass to the Measure-Object
cmdlet to show the over 158
results returned. You can tell by the verbose output that the start_cursor
is defined in the second call and that more results exist.
Bonus! Reduce Passed Parameters Through PSDefaultParameterValues
You may have noticed that in the $Params
block, you are passing the API Key, API Version, and API URI. As these three values very rarely change, it would be convenient not to have to do that every time. PowerShell has a mechanism to make this easier.
With the $PSDefaultParameterValues
variable, you can define default parameter values once placed in your profile. Every time a function is called, these values will be given if no other value is defined at runtime.
$PSDefaultParameterValues = @{
"Get-NotionBlock:APIKey" = 'secret_fS2A21NwTL4L6XWFTTdgmF3xboWiYXExKZ8Pw15oRMw'
"Get-NotionBlock:APIVersion" = '2022-06-28'
"Get-NotionBlock:APIURI" = 'https://api.notion.com/v1'
}
Making the same call as before, but simply passing the GUID
in means that you can use the function even easier!
Wrapping Up
This is just the start of your Notion PowerShell journey! This article showcases a single REST API call but lays the groundwork for more possibilities to come. Integrate Notion into your PowerShell scripts and take advantage of both technologies’ flexibility and power.
The same principles in play for this article hold for implementing all of the other methods, which will be shown in future articles!