---
title: "Building a Notion PowerShell Module: Part 2"
description: "Learn the next steps of building a PowerShell Notion module in this in-depth PowerShell tutorial by ATA Learning!"
canonical: "https://adamtheautomator.com/notion-powershell-2/"
---

# Building a Notion PowerShell Module: Part 2

> Learn the next steps of building a PowerShell Notion module in this in-depth PowerShell tutorial by ATA Learning!

Source: https://adamtheautomator.com/notion-powershell-2/

---

ATA Learning

Tap to hide

[

ATA Learning

](/)

*   [Home](/)
*   [Tutorials](/tutorials/)
*   [Instructors](/author/)
*   [Advertising](/advertising/)
*   [Recommended Resources](/resources/)
*   [About Adam](/about-adam/)

Search for:  

*   [](https://twitter.com/adbertram)
*   [](https://github.com/Adam-the-Automator)
*   [](https://www.linkedin.com/company/adam-the-automator-llc)
*   [](/feed/)

![Building a Notion PowerShell Module: Part 2](https://adamtheautomator.com/wp-content/uploads/2023/10/Notion-PowerShell.jpg)

# Building a Notion PowerShell Module: Part 2

[![](https://secure.gravatar.com/avatar/f08b754bc0dce1c76685f6afa93185ecfb6f861fd14f993286e06bba69eaeb8b?s=192&d=mm&r=g)Adam Listek](https://adamtheautomator.com/author/alistek/)27 October 20237 min. read

Categories: [IT Ops](/category/it-ops/)

Tags:[PowerShell](/tag/powershell/)

Table of Contents

*   [Prerequisites](#prerequisites)
*   [Blocks Everywhere](#blocks-everywhere)
*   [Creating a Fancier Block](#creating-a-fancier-block)
*   [Updating an Existing Block](#updating-an-existing-block)
*   [Removing a Block](#removing-a-block)
*   [Bringing it All Together](#bringing-it-all-together)
*   [Creating new Blocks through the New-NotionBlock Function](#creating-new-blocks-through-the-new-notionblock-function)
*   [Update Blocks with the Set-NotionBlock Function](#update-blocks-with-the-set-notionblock-function)
*   [Removing Old Blocks with the Remove-NotionBlock Function](#removing-old-blocks-with-the-remove-notionblock-function)
*   [Seeing it All in Action](#seeing-it-all-in-action)
*   [Next Time in Building a PowerShell Notion Module](#next-time-in-building-a-powershell-notion-module)

Building on the [previous Notion tutorial](https://adamtheautomator.com/notion-powershell/), you have learned how to create a Notion integration token, retrieve blocks from the Notion API, and wrap all of that into an advanced PowerShell function.

As valuable as getting a page’s blocks is, modifying or updating the blocks on a page is a stepping stone to building practical tools and integrations with other content. In this tutorial, learn how to add, update, and delete blocks through the Notion API, slowly building up a proper Notion API module in PowerShell!

## Prerequisites

To follow along in this tutorial, you only need a Notion account and [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3); here, PowerShell v7.3.7 is in use.

## Blocks Everywhere

Like the first tutorial, it’s best to start with some stripped-down code to learn the basics, before wrapping everything into more advanced functions. To add a new block to an existing page, you will use the same API call to retrieve blocks, but with a different HTML method, `PATCH`.

To tell Notion where you want the block to be, you must pass either the page ID or a block ID, if you want the block to be a child of a different block. As you can see from the below code, it is very similar to that of retrieving blocks. The difference is two-fold:

1.  The `Method` is now set to `PATCH`.
2.  There is a `Body` parameter that contains a JSON object. A standard PowerShell object is created but converted to JSON with the `ConvertTo-JSON` cmdlet set to the max depth of `100` to avoid issues with creation.

```powershell
$APIKey     = 'secret_fMnSn52qUruc1k0M7CargoM94mgS3loo3vdVBSzq74W'
$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"  = 'PATCH'
    "URI"     = ("{0}/blocks/{1}/children" -F $APIURI, [GUID]::new($GUID))
    "Body"    = @{
        "children" = @(
            @{
                "paragraph" = @{
                    "rich_text" = @(
                        @{
                            "text" = @{
                                "content" = "This is written by a robot!"
                            }
                        }
                    )
                }
            }
        )
    } | ConvertTo-JSON -Depth 100
}

$Result = Invoke-RestMethod @Params
```

There is no output from a successful call with this code, but you can immediately see the results of the call. On the left is the content before the code is run, and on the right is after.

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-130.png)

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-131.png)

What if you wanted to create a block as the child of another block? You can leverage the previously created cmdlet, `Get-NotionBlock`, to find the last block ID and pass that into the code to create the new block. The code changes you are going to make are two lines. The first code addition retrieves all page blocks using the previously created function.

Next, instead of passing the page ID, you will pass the results of the `$Blocks` object, but using array notation to find the last one with the `[-1]` notation, and get the `id`. When you rerun the code, with these changes, the final block will have a new child block, as shown.

```powershell
$Blocks = Get-NotionBlock -GUID 'a2b3646de9414df4874d56153139b618'
$GUID   = $Blocks[-1].id
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-132.png)

## Creating a Fancier Block

So far, you have created a paragraph block with just plain text. How about creating a callout with rich text objects contained within? The same structure as before will be used, with the page ID, but creating a more complex JSON object.

Using the same code as above, you are replacing the `Body` parameter with the below code, which will create a callout, set the background color, and create bold initial content.

```powershell
@{
    "children" = @(
        @{
            "callout" = @{
                "rich_text" = @(
                    @{
                        "text" = @{
                            "content" = "Just a friendly reminder of the three laws of robotics."
                        }
                        "annotations" = @{
                            "bold" = $True
                        }
                    }
                )
                "color" = "blue_background"
            }
        }
    )
}
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-133.png)

You may have noticed that the text there references the three laws of robotics, but the callout block can only support [`rich_text`](https://developers.notion.com/reference/rich-text)in the initial creation. Thankfully, you have already learned how to append child blocks to an existing one!

To fix this, you will create the list block as a child of the callout block. Before that, you must retrieve the callout and the ID associated with the block. To do this, you will use the `Get-NotionBlock` function and filter the results to the [`callout`](https://developers.notion.com/reference/block#callout) type, finally selecting the `id`.

```powershell
$Blocks = Get-NotionBlock -GUID 'a2b3646de9414df4874d56153139b618'
$Blocks | Where-Object type -EQ 'callout' | Select-Object id
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-134.png)

With the ID in hand, craft the new block, changing the `$GUID` value to the ID you previously located. Run the code, and you will see the following result.

```powershell
"Body" = @{
    "children" = @(
        @{
            "bulleted_list_item" = @{
                "rich_text" = @(
                    @{
                        "text" = @{
                            "content" = "A robot may not injure a human being or, through inaction, allow a human being to come to harm."
                        }
                    }
                )                
            }
        }
        @{
            "bulleted_list_item" = @{
                "rich_text" = @(  
                    @{
                        "text" = @{
                            "content" = "A robot must obey the orders given it by human beings except where such orders would conflict with the First Law."
                        }
                    }
                )
            }
        }
        @{
            "bulleted_list_item" = @{
                "rich_text" = @(  
                    @{
                        "text" = @{
                            "content" = "A robot must protect its own existence as long as such protection does not conflict with the First or Second Law."
                        }
                    }
                )
            }
        }
    )
} | ConvertTo-JSON -Depth 100
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-135.png)

## Updating an Existing Block

With all the blocks created, a more in-your-face warning for the three laws would be warranted. Instead of removing and re-creating, updating the content of the callout is doable.

The change is to the API URL and the body code. Change the API to: `("{0}/blocks/{1}" -F $APIURI, [GUID]::new($GUID))`, which removes the `children` from the end. This API call also uses the `PATCH` method as well.

```powershell
"Body"    = @{
    "callout" = @{
        "rich_text" = @(
            @{
                "text" = @{
                    "content" = "A strong reminder of the three laws of robotics."
                }
                "annotations" = @{
                    "bold" = $True
                    "color" = 'red_background'
                }
            }
        )
    }
} | ConvertTo-JSON -Depth 100
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-136.png)

## Removing a Block

With all these changes, you may need to remove one. To do so is similar to the previous API calls. This time, you will use the `DELETE` method, which sets a block (or page) to be archived and in the trash (making the content recoverable).

Run the following to remove the previously located `callout` block. Which will remove it from the page.

```powershell
$APIKey     = 'secret_fMnSn52qUruc1k0M7CargoM94mgS3loo3vdVBSzq74W'
$APIURI     = 'https://api.notion.com/v1'
$APIVersion = '2022-06-28'
$GUID       = '97b47389-befa-43d5-a2ee-b08f3ae602f9'

$Params = @{
    "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
    }
    "Method"  = 'DELETE'
    "URI"     = ("{0}/blocks/{1}" -F $APIURI, [GUID]::new($GUID))
}

$Result = Invoke-RestMethod @Params
```

![](https://adamtheautomator.com/wp-content/uploads/2023/10/image-137.png)

## Bringing it All Together

Like the previous tutorial, to continue building out this PowerShell Notion module, it’s time to wrap the code into advanced functions and add to the module. The three different functions that you are creating are:

*   Creating a new block – `New-NotionBlock`
*   Updating an existing block – `Set-NotionBlock`
*   Removing an existing block – `Remove-NotionBlock`

### Creating new Blocks through the `New-NotionBlock` Function

Similar to the original advanced function you have created, here is the `New-NotionBlock` function in all its glory. The significant changes are the addition of support for:

*   **What If** – Adding [`SupportsShouldProcess = $True`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute?view=powershell-7.3#supportsshouldprocess) to the `CmdletBinding` declaration allows operations to be wrapped in an `If` statement for `$PSCmdlet.ShouldProcess` to see what operation will occur first.
*   **Pipeline Input** – For the `$GUID` parameter, support pipeline input by the [`[Parameter(ValueFromPipelineByPropertyName = $True)]`](https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.parameterattribute.valuefrompipelinebypropertyname?view=powershellsdk-7.3.0) declaration.
*   **Pipeline Aliases** – To ensure that the incoming `ID` of an object passes to the right place, give the `$GUID` parameter an alias of `ID` that works in conjunction with the pipeline values.

With all of that in place, the function now creates a new block based on the GUID of the page, or parent block, and the declared content. This outputs the created block for later use in the pipeline.

```powershell
Function New-NotionBlock {
  [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,

    $Content
  )

  Process {
    $Params = @{
      "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
      }
      "Method" = 'PATCH'
      "URI"    = ("{0}/blocks/{1}/children" -F $APIURI, [GUID]::new($GUID))
      "Body"   = $Content | ConvertTo-JSON -Depth 100
    }

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

    If ($PSCmdlet.ShouldProcess($GUID,"Adding Block")) {
      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.results
      }
    }
  }
}
```

### Update Blocks with the `Set-NotionBlock` Function

Similar to creating a block, the `Set-NotionBlock` function replaces the existing content of a block with that of the new content you define. The structure of the function is nearly identical with the only change being the API call itself.

```powershell
Function Set-NotionBlock {
  [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,

    $Content
  )

  Process {
    $Params = @{
      "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
      }
      "Method" = 'PATCH'
      "URI"    = ("{0}/blocks/{1}" -F $APIURI, [GUID]::new($GUID))
      "Body"   = $Content | ConvertTo-JSON -Depth 100
    }

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

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

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

    $Result
  }
}
```

### Removing Old Blocks with the `Remove-NotionBlock` Function

Finally, the `Remove-NotionBlock` function rounds out the functions with the ability to remove a block but again with a similar structure to the prior functions. The primary difference is that the result of the operation is not output, as there is none.

```powershell
Function Remove-NotionBlock {
  [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
  )

  Process {
    $Params = @{
      "Headers" = @{
        "Authorization"  = "Bearer {0}" -F $APIKey
        "Content-type"   = "application/json"
        "Notion-Version" = "{0}" -F $APIVersion
      }
      "Method" = 'DELETE'
      "URI"    = ("{0}/blocks/{1}" -F $APIURI, [GUID]::new($GUID))
    }

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

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

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

### Seeing it All in Action

What does this look like when you use everything together? You can create a block, update a block, and ultimately remove the block, by piping the content to each function. Here, it helps to sleep for a few seconds after the update operation to see the removal in action.

```powershell
$Block = New-NotionBlock -GUID 'a2b3646de9414df4874d56153139b618' -Content @{
    "children" = @(
        @{
            "paragraph" = @{
                "rich_text" = @(
                    @{
                        "text" = @{
                            "content" = "This is written by a robot!"
                        }
                    }
                )
            }
        }
    )
} | Set-NotionBlock -Content @{
    "paragraph" = @{
        "rich_text" = @(
            @{
                "text" = @{
                    "content" = "This is UPDATED by a robot!"
                }
            }
        )
    }
}

Start-Sleep -Seconds 3

$Block | Remove-NotionBlock
```

## Next Time in Building a PowerShell Notion Module

With the addition of these three functions and the previously created function, you now have a full set of functions to manipulate blocks as much as you want. In the next article of the series, you will learn how to work with Databases and Pages!

Share this article

[Share on X](https://twitter.com/intent/tweet?url=https%3A%2F%2Fadamtheautomator.com%2Fnotion-powershell-2%2F&text=Building%20a%20Notion%20PowerShell%20Module%3A%20Part%202)[Share on Facebook](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fadamtheautomator.com%2Fnotion-powershell-2%2F)[Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fadamtheautomator.com%2Fnotion-powershell-2%2F)

## Related Posts

![](https://adamtheautomator.com/wp-content/uploads/2026/06/26995-troubleshoot-dns-issues-powershell-codex.webp)

### [Troubleshoot DNS Issues with PowerShell](/troubleshoot-dns-issues-powershell/)

Troubleshoot DNS issues with PowerShell by testing name resolution, DNS client settings, cache entries, and network connectivity in a repeatable workflow.

![](https://adamtheautomator.com/wp-content/uploads/2025/10/image_2025-10-24_095322075.png)

### [Migrating from PowerShell 6 to 7.5: Breaking Changes/New Features](/migrating-powershell-6-to-7-5/)

Migrate from PowerShell Core 6 to 7.5: breaking changes, features, and testing tips.

![](https://adamtheautomator.com/wp-content/uploads/2025/06/featured-image-6.png)

### [How to Add Timeouts to Pester Tests with PowerShell Runspaces](/pester-test-timeout-runspaces/)

Prevent Pester tests from hanging indefinitely using PowerShell runspaces. Learn to handle variable scoping, module loading, TestDrive access, and stream capture challenges with timeout protection.

## Categories

*   [IT Ops](/category/it-ops/)
*   [Cloud](/category/cloud/)
*   [DevOps](/category/devops/)
*   [Home Ops](/category/home-ops/)
*   [Information Security](/category/infosec/)
*   [Software Development](/category/software-development/)

## Site

*   [Home](/)
*   [Tutorials](/tutorials/)
*   [Instructors](/author/)
*   [Advertising](/advertising/)
*   [Recommended Resources](/resources/)
*   [About Adam](/about-adam/)

Copyright 2026© ATA Learning | [Privacy Policy](/privacy/)
