notionapi Go Client

repository·main·Indexed 20 days ago

https://github.com/jomei/notionapi

A Golang implementation of an API client for the Notion API (version 2022-06-28), enabling programmatic interaction with Notion pages, databases, blocks, users, and comments. It includes support for OAuth authentication via AuthenticationClient, database management through DatabaseClient, and block manipulation using BlockService.

Tokens
19.1K
Snippets
62
Records
77
Agent score
63%

What's inside notionapi

  1. Initialize the Notion API client

    main

    To use the library, import github.com/jomei/notionapi and initialize a new client using notionapi.NewClient. You must provide a Notion integration token obtained from the Notion developer portal.

    import "github.com/jomei/notionapi"
    
    client := notionapi.NewClient("your_integration_token")
  2. Understand the Property interface and polymorphic decoding

    main

    In notionapi, Notion database and page properties are handled using a polymorphic Property interface. Because Notion returns different JSON structures based on the type field of a property, the library uses a custom UnmarshalJSON implementation on the Properties map and PropertyArray type to automatically decode the correct concrete struct.

    To work with properties, you should use the Property interface to access common metadata via GetID() and GetType(), then use a type assertion to access the specific data fields of the concrete property type (e.g., TitleProperty, NumberProperty, SelectProperty).

  3. Understand the PropertyConfig interface and types

    main

    In the notionapi package, database properties are represented by the PropertyConfig interface. This interface allows you to treat different Notion property types (like Title, Number, Select, etc.) polymorphically.

    Every implementation of PropertyConfig must provide:

    • GetType() PropertyConfigType: Returns the specific Notion property type.
    • GetID() PropertyID: Returns the unique identifier for the property.

    Properties are typically managed via the PropertyConfigs map type, which handles the complex JSON unmarshaling required to turn a raw Notion API response into the correct concrete struct type based on the type field.

  4. Use the Block interface to handle different block types

    main

    In the notionapi library, all Notion blocks implement the Block interface. This allows you to work with a collection of heterogeneous blocks (a Blocks slice) and use type assertions or the interface methods to access specific data.

    Common interface methods:

    • GetType() BlockType: Returns the specific type of the block (e.g., paragraph, heading_1, to_do).
    • GetID() BlockID: Returns the unique identifier.
    • GetRichTextString() string: A helper method that returns the plain text content of the block (concatenating rich text where applicable).
    func processBlocks(blocks notion.Blocks) {
        for _, b := range blocks {
            fmt.Printf("Block ID: %s, Type: %s, Text: %s\n", 
                b.GetID(), 
                b.GetType(), 
                b.GetRichTextString(),
            )
    
            // Type assertion to access specific block properties
            if todo, ok := b.(*notion.ToDoBlock); ok {
                fmt.Printf("Is Checked: %v\n", todo.ToDo.Checked)
            }
        }
    }
  5. Call the Notion API

    main

    Once initialized, you can interact with Notion resources through the client's sub-services (e.g., client.Page). Most methods require a context.Context and the specific resource ID.

    page, err := client.Page.Get(context.Background(), "your_page_id")
    if err != nil {
        // Handle the error
    }
  6. Construct compound filters with AndCompoundFilter and OrCompoundFilter

    main

    To combine multiple filter conditions in a Notion query, use AndCompoundFilter or OrCompoundFilter. These types implement the Filter interface and allow you to nest filters logically.

    • AndCompoundFilter: A slice of Filter objects that must all be true.
    • OrCompoundFilter: A slice of Filter objects where at least one must be true.

    These types are automatically marshaled into the correct JSON structure ({"and": [...]} or {"or": [...]}) required by the Notion API.

    // Example of an AND filter combining two property filters
    filter := notionapi.AndCompoundFilter{
    	notionapi.PropertyFilter{
    		Property: "Status",
    		Status: &notionapi.StatusFilterCondition{
    			Equals: "Done",
    		},
    	},
    	notionapi.PropertyFilter{
    		Property: "Priority",
    		Select: &notionapi.SelectFilterCondition{
    			Equals: "High",
    		},
    	},
    }
  7. Configure the Client using ClientOptions

    main

    The NewClient constructor accepts functional options to override default settings. Use these to customize the HTTP client, API version, retry logic, or OAuth credentials.

    Available options:

    • WithHTTPClient(client *http.Client): Overrides the default http.Client.
    • WithVersion(version string): Overrides the Notion-Version header (defaults to 2022-06-28).
    • WithRetry(retries int): Overrides the number of retry attempts for 429 Too Many Requests errors (defaults to 3).
    • WithOAuthAppCredentials(id, secret string): Sets the OAuth app ID and secret used for Basic authentication when fetching tokens.
    import (
    	"net/http"
    	"github.com/jomei/jomei/notionapi"
    )
    
    client := notionapi.NewClient(
    	notionapi.Token("your_token"),
    	notionapi.WithHTTPClient(&http.Client{Timeout: 10 * time.Second}),
    	notionapi.WithVersion("2022-06-28"),
    	notionapi.WithRetry(5),
    	notionapi.WithOAuthAppCredentials("client_id", "client_secret"),
    )
  8. Manage Notion blocks with BlockService

    main

    The BlockService interface provides methods to interact with Notion blocks. You can use a BlockClient to implement this service for retrieving, updating, appending, or deleting blocks.

    Key operations include:

    • Get(ctx, id): Retrieves a single block by its BlockID.
    • GetChildren(ctx, id, pagination): Returns a paginated list of child blocks for a specific block.
    • AppendChildren(ctx, id, request): Appends new blocks as children to a parent block. Note that blocks appended via this method cannot be moved elsewhere via the API.
    • Update(ctx, id, request): Updates the content of an existing block. The update replaces the entire value for a given field; omitting a field leaves its current value unchanged.
    • Delete(ctx, id): Archives a block (moves it to the Trash).
    // Example usage of BlockService methods
    // Assuming 'client' is an initialized *BlockClient
    
    // 1. Get a block
    block, err := client.Get(ctx, notion.BlockID("block_id"))
    
    // 2. Append children
    appendReq := &notion.AppendBlockChildrenRequest{
        Children: notion.Blocks{
            &notion.ParagraphBlock{
                BasicBlock: notion.BasicBlock{Type: notion.BlockTypeParagraph},
                Paragraph: notion.Paragraph{RichText: []notion.RichText{{PlainText: "Hello World"}}},
            },
        },
    }
    client.AppendChildren(ctx, notion.BlockID("parent_id"), appendReq)
    
    // 3. Update a block
    updateReq := &notion.BlockUpdateRequest{
        ToDo: &notion.ToDo{
            RichText: []notion.RichText{{PlainText: "Updated Task"}},
            Checked:  true,
        },
    }
    client.Update(ctx, notion.BlockID("todo_id"), updateReq)
  9. Use PropertyConfigs to unmarshal Notion property data

    main

    The PropertyConfigs type is a map of string to PropertyConfig. It implements a custom UnmarshalJSON method that automatically detects the property type from the JSON payload and instantiates the correct concrete struct (e.g., TitlePropertyConfig, NumberPropertyConfig).

    When you receive a JSON object representing Notion database properties, unmarshaling it into PropertyConfigs will allow you to access type-specific fields after performing a type assertion.

    // Example of how PropertyConfigs handles polymorphic JSON
    var configs PropertyConfigs
    err := json.Unmarshal(jsonData, &configs)
    if err != nil {
        // handle error
    }
    
    // Accessing a specific property type via type assertion
    for name, config := range configs {
        switch c := config.(type) {
        case *notionapi.NumberPropertyConfig:
            fmt.Printf("Property %s is a number with format: %v\n", name, c.Number.Format)
        case *notionapi.TitlePropertyConfig:
            fmt.Printf("Property %s is a title\n", name)
        }
    }
  10. Create a comment with CommentClient.Create

    main

    Use CommentClient.Create to add a new comment to either a page or an existing discussion thread.

    Requirements: Exactly one of the following must be provided in the CommentCreateRequest:

    1. Parent: To add a comment to a page.
    2. DiscussionID: To add a comment to an existing discussion thread.

    Returns the created Comment object.

    err := client.Comment.Create(ctx, &notionapi.CommentCreateRequest{
        Parent: notionapi.Parent{
            Page: notionapi.PageID("your-page-id"),
        },
        RichText: []notionapi.RichText{
            { Text: notionapi.Text{ Content: "Hello world" } },
        },
    })