
Notion.so is a web-based note-taking app. https://github.com/kjk/notionapi is a Go library that uses reverse-engineered API to download Notion pages. Those pages can then be converted to HTML.
Full API docs are at https://godoc.org/github.com/kjk/notionapi
Real-life usage scenario
One example of using the library is publishing a website. https://blog.kowalczyk.info is generated from the content stored in Notion and deployed to Netlify using a Go program.
Downloading a page
Content of Notion consists of pages.
Each page has a unique id. The id is the last part of Notion URL. For example page
https://www.notion.so/Test-page-all-c969c9455d7c4dd79c7f860f3ace6429
has id c969c9455d7c4dd79c7f860f3ace6429
.
You can retrieve the content of public page given its id:
import (
"log"
"github.com/kjk/notionapi"
)
client := ¬ionapi.Client{}
pageID := "c969c9455d7c4dd79c7f860f3ace6429"
page, err := client.DownloadPage(pageID)
if err != nil {
log.Fatalf("DownloadPage() failed with %s\n", err)
}
// look at page.Page to see structured content
Accessing non-public pages
To access non-public pages you need to find out authentication token.
Auth token is the value of
token_v2
cookie.
In Chrome: open developer tools (Menu
More Tools\Developer Tools
), navigate to Application
tab, look under Storage \ Cookies
and copy the value of token_v2
cookie. You can do similar things in other browsers.
Then configure
Client
with access token::
client := ¬ionapi.Client{}
client.AuthToken = "value of token_v2 value"
Anatomy of a Notion page
A notion page consists of blocks. A block represents a piece of content: a text block, an image, a block of code, a sub-page, a list item etc. Each block has a
Block.Type
represented by one of the Block*
constants.
Some blocks can have sub-blocks
Block.Content
.
Page.Root
is the top-level block representing a page.
Getting a list of sub-pages
Notion pages are nested. If you have a
notionapi.Page
you can find out list of sub-pages by recursively traversing blocks:
#note could improve it
func findSubPageIDs(page *notionapi.Page) []string {
blocks := page.Root.Content
pageIDs := map[string]struct{}{}
seen := map[string]struct{}{}
toVisit := blocks
for len(toVisit) > 0 {
block := toVisit[0]
toVisit = toVisit[1:]
id := notionapi.NormalizeID(block.ID)
if block.Type == notionapi.BlockPage {
pageIDs[id] = struct{}{}
seen[id] = struct{}{}
}
for _, b := range block.Content {
if b == nil {
continue
}
id := notionapi.NormalizeID(block.ID)
if _, ok := seen[id]; ok {
continue
}
toVisit = append(toVisit, b)
}
}
res := []string{}
for id := range pageIDs {
res = append(res, id)
}
sort.Strings(res)
return res
}
Note that we keep track of seen ids to avoid infinite loops if blocks form loops.
You can see a complete example of recursively downloading and caching notion pages at https://github.com/kjk/blog/blob/master/notion_import.go
Converting pages to HTML
To convert a page to HTML, recursively traverse blocks starting at
Page.Root.Content
and convert each block to HTML. For a full example see https://github.com/kjk/blog/blob/master/notion_to_html.go
Writing data to Notion
Currently the library has very limited capabilities for writing data to Notion.
You can change page title and change format of the page.
For example, to change page title:
page, err := client.DownloadPage(pageID)
if err != nil {
log.Fatalf("DownloadPage() failed with %s\n", err)
}
err = page.SetTitle("new title")
if err != nil {
log.Fatalf("SetTitle() failed with %s\n", err)
}
There's no technical reason write capabilities are so limited, more could be implemented. If you need more functionality, open an issue at https://github.com/kjk/notionapi/issues