-
Notifications
You must be signed in to change notification settings - Fork 883
docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes #421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||||||||||||||||||
| # Google Docs Cookbook | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Hand-written recipes for common Google Docs API patterns. These complement the auto-generated [SKILL.md](./SKILL.md). | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ## Working with Tabs | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Google Docs supports multiple tabs within a single document. The API requires specific patterns for reading, creating, and writing to tabs. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Reading all tabs | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Use `includeTabsContent: true` to get all tabs. Without it, only the first tab's content is returned in the legacy `body` field. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents get \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID", "includeTabsContent": true}' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Response structure: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```text | ||||||||||||||||||||||||||||||||||||||||||
| doc.tabs[] # Array of top-level tabs | ||||||||||||||||||||||||||||||||||||||||||
| .tabProperties.tabId # Tab identifier (e.g., "t.abc123") | ||||||||||||||||||||||||||||||||||||||||||
| .tabProperties.title # Tab display name | ||||||||||||||||||||||||||||||||||||||||||
| .tabProperties.index # Position (0-based) | ||||||||||||||||||||||||||||||||||||||||||
| .documentTab.body.content[] # Paragraphs, tables, etc. | ||||||||||||||||||||||||||||||||||||||||||
| .childTabs[] # Nested child tabs (same structure) | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| > **Note:** Tabs can be nested. When searching for a tab by title, recursively check `childTabs`. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Creating a new tab | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Use `addDocumentTab` in a batchUpdate request. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{"requests": [{"addDocumentTab": {"tabProperties": {"title": "My New Tab"}}}]}' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The response includes the new tab's ID: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```text | ||||||||||||||||||||||||||||||||||||||||||
| replies[0].addDocumentTab.tabProperties.tabId → "t.xyz789" | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| > **Gotcha:** The request type is `addDocumentTab`, not `createTab`. Using `createTab` returns a validation error. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Writing content to a specific tab | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Include `tabId` in the `location` object of any content request. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{ | ||||||||||||||||||||||||||||||||||||||||||
| "requests": [{ | ||||||||||||||||||||||||||||||||||||||||||
| "insertText": { | ||||||||||||||||||||||||||||||||||||||||||
| "text": "Hello from a specific tab!\n", | ||||||||||||||||||||||||||||||||||||||||||
| "location": {"index": 1, "tabId": "TAB_ID"} | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }] | ||||||||||||||||||||||||||||||||||||||||||
| }' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| > **Important:** `tabId` must also be included in `range` objects for formatting requests like `updateParagraphStyle`. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Renaming a tab | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{ | ||||||||||||||||||||||||||||||||||||||||||
| "requests": [{ | ||||||||||||||||||||||||||||||||||||||||||
| "updateDocumentTabProperties": { | ||||||||||||||||||||||||||||||||||||||||||
| "tabId": "TAB_ID", | ||||||||||||||||||||||||||||||||||||||||||
| "documentTabProperties": {"title": "New Title"}, | ||||||||||||||||||||||||||||||||||||||||||
| "fields": "title" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }] | ||||||||||||||||||||||||||||||||||||||||||
| }' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Deleting a tab | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{"requests": [{"deleteTab": {"tabId": "TAB_ID"}}]}' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ## Inserting Formatted Content | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The `+write` helper inserts plain text only. For structured content with headings, bold, or styles, use `batchUpdate` with multiple requests. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Pattern: insert text then apply styles | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The key principle: insert all text in a **single request**, then apply formatting using character ranges. This avoids index-shifting issues between requests. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{ | ||||||||||||||||||||||||||||||||||||||||||
| "requests": [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| "insertText": { | ||||||||||||||||||||||||||||||||||||||||||
| "text": "My Heading\nBody paragraph text.\n", | ||||||||||||||||||||||||||||||||||||||||||
| "location": {"index": 1, "tabId": "TAB_ID"} | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| "updateParagraphStyle": { | ||||||||||||||||||||||||||||||||||||||||||
| "paragraphStyle": {"namedStyleType": "HEADING_1"}, | ||||||||||||||||||||||||||||||||||||||||||
| "range": {"startIndex": 1, "endIndex": 12, "tabId": "TAB_ID"}, | ||||||||||||||||||||||||||||||||||||||||||
| "fields": "namedStyleType" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||
| }' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Available paragraph styles | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| | Named Style | Usage | | ||||||||||||||||||||||||||||||||||||||||||
| |-------------|-------| | ||||||||||||||||||||||||||||||||||||||||||
| | `TITLE` | Document title | | ||||||||||||||||||||||||||||||||||||||||||
| | `HEADING_1` – `HEADING_6` | Section headings | | ||||||||||||||||||||||||||||||||||||||||||
| | `NORMAL_TEXT` | Body text (default) | | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Applying bold or italic | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Use `updateTextStyle` with a character range to apply styles like bold or italic. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||
| gws docs documents batchUpdate \ | ||||||||||||||||||||||||||||||||||||||||||
| --params '{"documentId": "DOC_ID"}' \ | ||||||||||||||||||||||||||||||||||||||||||
| --json '{ | ||||||||||||||||||||||||||||||||||||||||||
| "requests": [{ | ||||||||||||||||||||||||||||||||||||||||||
| "updateTextStyle": { | ||||||||||||||||||||||||||||||||||||||||||
| "textStyle": {"bold": true}, | ||||||||||||||||||||||||||||||||||||||||||
| "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, | ||||||||||||||||||||||||||||||||||||||||||
| "fields": "bold" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }] | ||||||||||||||||||||||||||||||||||||||||||
| }' | ||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+136
to
+148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example for
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Replace `"bold": true` with `"italic": true` for italic, or combine both by setting `"textStyle": {"bold": true, "italic": true}` and `"fields": "bold,italic"`. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### Tips for batch formatting | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| - **Insert first, format second.** A single `insertText` followed by multiple `updateParagraphStyle`/`updateTextStyle` requests avoids index math headaches. | ||||||||||||||||||||||||||||||||||||||||||
| - **Track positions manually.** Each character (including `\n`) advances the index by 1. Index 1 is the start of the document body. | ||||||||||||||||||||||||||||||||||||||||||
| - **Atomic batches.** All requests in a single `batchUpdate` are atomic — if any request fails, none are applied. | ||||||||||||||||||||||||||||||||||||||||||
| - **Tab targeting.** When writing to a non-default tab, every `location` and `range` must include the `tabId`. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ## Valid batchUpdate Request Types | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Quick reference for all supported request types: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| | Category | Requests | | ||||||||||||||||||||||||||||||||||||||||||
| |----------|----------| | ||||||||||||||||||||||||||||||||||||||||||
| | **Tabs** | `addDocumentTab`, `deleteTab`, `updateDocumentTabProperties` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Text** | `insertText`, `deleteContentRange`, `replaceAllText`, `replaceNamedRangeContent` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Formatting** | `updateTextStyle`, `updateParagraphStyle`, `updateDocumentStyle`, `updateSectionStyle` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Lists** | `createParagraphBullets`, `deleteParagraphBullets` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Tables** | `insertTable`, `insertTableRow`, `insertTableColumn`, `deleteTableRow`, `deleteTableColumn`, `mergeTableCells`, `unmergeTableCells`, `updateTableCellStyle`, `updateTableColumnProperties`, `updateTableRowStyle`, `pinTableHeaderRows` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Objects** | `insertInlineImage`, `replaceImage`, `deletePositionedObject`, `insertPerson`, `insertDate` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Structure** | `insertPageBreak`, `insertSectionBreak`, `createHeader`, `deleteHeader`, `createFooter`, `deleteFooter`, `createFootnote` | | ||||||||||||||||||||||||||||||||||||||||||
| | **Named Ranges** | `createNamedRange`, `deleteNamedRange` | | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ## Limitations of `+write` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The `docs +write` helper has two limitations to be aware of: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 1. **No tab support** — always appends to the first tab. Use `batchUpdate` with `tabId` in `location` to target a specific tab (see [Writing content to a specific tab](#writing-content-to-a-specific-tab)). | ||||||||||||||||||||||||||||||||||||||||||
| 2. **Plain text only** — no formatting. Use `batchUpdate` with `updateParagraphStyle`/`updateTextStyle` for structured content (see [Inserting Formatted Content](#inserting-formatted-content)). | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
endIndexforupdateParagraphStyleappears to be off-by-one. The text for the first paragraph isMy Heading\n, which has a length of 11 characters and occupies indices 1 through 11. Since theendIndexin a Google Docs API range is exclusive, it should be12to cover the entire paragraph including the newline character. An incorrect range can be confusing and lead to subtle bugs in user scripts.For reference, the text being formatted is
My Heading\nfrom line 109.