@@ -19,6 +19,9 @@ const (
1919 MaxRetries = 3
2020 BaseBackoff = 1 * time .Second
2121 MaxBackoff = 16 * time .Second
22+ // BlockChunkSize defines the maximum number of blocks to send in a single API call
23+ // Notion's limit is 100, but we use 50 for better reliability with large documents
24+ BlockChunkSize = 50
2225)
2326
2427// Client handles Notion API interactions
@@ -61,46 +64,62 @@ func (c *Client) formatPageID(pageID string) string {
6164 cleaned [0 :8 ], cleaned [8 :12 ], cleaned [12 :16 ], cleaned [16 :20 ], cleaned [20 :32 ])
6265}
6366
64- // AppendBlockChildren appends blocks to a page or block
65- func (c * Client ) AppendBlockChildren (ctx context.Context , blockID string , blocks []Block ) error {
66- formattedID := c .formatPageID (blockID )
67+ // processBlocksInChunks processes blocks in chunks to respect Notion's API limits
68+ // Notion's API has a limit of 100 blocks per request. This function splits
69+ // blocks into smaller chunks and processes them sequentially with a small
70+ // delay between chunks to be gentle on the API.
71+ func (c * Client ) processBlocksInChunks (ctx context.Context , blocks []Block , processFn func (ctx context.Context , chunk []Block ) error ) error {
72+ if len (blocks ) == 0 {
73+ return nil
74+ }
6775
68- // Split blocks into chunks of 25 for better reliability with large documents
69- chunkSize := 25
70- for i := 0 ; i < len (blocks ); i += chunkSize {
71- end := i + chunkSize
76+ for i := 0 ; i < len (blocks ); i += BlockChunkSize {
77+ end := i + BlockChunkSize
7278 if end > len (blocks ) {
7379 end = len (blocks )
7480 }
7581
7682 chunk := blocks [i :end ]
77- req := AppendBlockChildrenRequest {Children : chunk }
78-
79- if err := c .makeRequest (ctx , "PATCH" , fmt .Sprintf ("/blocks/%s/children" , formattedID ), req , nil ); err != nil {
80- return fmt .Errorf ("failed to append blocks (chunk %d-%d): %w" , i + 1 , end , err )
83+ if err := processFn (ctx , chunk ); err != nil {
84+ return fmt .Errorf ("failed to process blocks (chunk %d-%d): %w" , i + 1 , end , err )
8185 }
8286
8387 if c .verbose {
84- fmt .Fprintf (os .Stderr , "Uploaded %d blocks (chunk %d-%d)\n " , len (chunk ), i + 1 , end )
88+ fmt .Fprintf (os .Stderr , "Processed %d blocks (chunk %d-%d)\n " , len (chunk ), i + 1 , end )
8589 }
8690
8791 // Small pause between chunks to be nice to the API
8892 if end < len (blocks ) {
89- time .Sleep (100 * time .Millisecond )
93+ time .Sleep (10 * time .Millisecond )
9094 }
9195 }
9296
9397 return nil
9498}
9599
100+ // AppendBlockChildren appends blocks to a page or block
101+ // Blocks are automatically split into chunks to respect Notion's 100-block limit per API call.
102+ // Uses a chunk size of 50 for better reliability with large documents.
103+ func (c * Client ) AppendBlockChildren (ctx context.Context , blockID string , blocks []Block ) error {
104+ formattedID := c .formatPageID (blockID )
105+
106+ return c .processBlocksInChunks (ctx , blocks , func (ctx context.Context , chunk []Block ) error {
107+ req := AppendBlockChildrenRequest {Children : chunk }
108+ return c .makeRequest (ctx , "PATCH" , fmt .Sprintf ("/blocks/%s/children" , formattedID ), req , nil )
109+ })
110+ }
111+
96112// CreatePage creates a new page under a parent page
113+ // The page is created first without children, then blocks are appended in chunks
114+ // to avoid Notion's 100-block limit per API call.
97115func (c * Client ) CreatePage (ctx context.Context , parentID , title string , blocks []Block ) (* PageResponse , error ) {
98116 formattedParentID := c .formatPageID (parentID )
99117 titleText := []RichText {{
100118 Type : "text" ,
101119 Text : & Text {Content : title },
102120 }}
103121
122+ // Create the page without children first to avoid the 100-block limit
104123 req := CreatePageRequest {
105124 Parent : Parent {
106125 Type : "page_id" ,
@@ -109,14 +128,53 @@ func (c *Client) CreatePage(ctx context.Context, parentID, title string, blocks
109128 Properties : PageProperties {
110129 Title : TitleProperty {Title : titleText },
111130 },
112- Children : blocks ,
131+ // Don't include children in the initial creation
113132 }
114133
115134 var resp PageResponse
116135 if err := c .makeRequest (ctx , "POST" , "/pages" , req , & resp ); err != nil {
117136 return nil , fmt .Errorf ("failed to create page: %w" , err )
118137 }
119138
139+ // If there are blocks to add, append them in chunks after page creation
140+ if len (blocks ) > 0 {
141+ if err := c .AppendBlockChildren (ctx , resp .ID , blocks ); err != nil {
142+ return nil , fmt .Errorf ("failed to add content to page: %w" , err )
143+ }
144+ }
145+
146+ return & resp , nil
147+ }
148+
149+ // CreatePageInDatabase creates a new page in a database
150+ // The page is created first without children, then blocks are appended in chunks
151+ // to avoid Notion's 100-block limit per API call.
152+ // Note: When creating in a database, the properties must match the database schema
153+ func (c * Client ) CreatePageInDatabase (ctx context.Context , databaseID string , properties PageProperties , blocks []Block ) (* PageResponse , error ) {
154+ formattedDatabaseID := c .formatPageID (databaseID )
155+
156+ // Create the page without children first to avoid the 100-block limit
157+ req := CreatePageRequest {
158+ Parent : Parent {
159+ Type : "database_id" ,
160+ DatabaseID : formattedDatabaseID ,
161+ },
162+ Properties : properties ,
163+ // Don't include children in the initial creation
164+ }
165+
166+ var resp PageResponse
167+ if err := c .makeRequest (ctx , "POST" , "/pages" , req , & resp ); err != nil {
168+ return nil , fmt .Errorf ("failed to create page in database: %w" , err )
169+ }
170+
171+ // If there are blocks to add, append them in chunks after page creation
172+ if len (blocks ) > 0 {
173+ if err := c .AppendBlockChildren (ctx , resp .ID , blocks ); err != nil {
174+ return nil , fmt .Errorf ("failed to add content to page: %w" , err )
175+ }
176+ }
177+
120178 return & resp , nil
121179}
122180
0 commit comments