# How to Batch Modify Gmail Labels with gogcli: Commands and Code Examples

> Easily batch modify Gmail labels using gogcli commands. Learn how to add or remove labels from multiple emails with code examples and efficient API calls.

- Repository: [Peter Steinberger/gogcli](https://github.com/steipete/gogcli)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Use the `gog gmail batch modify` command with `--add` and `--remove` flags to modify labels on multiple messages via a single API call, automatically resolving label names to IDs.**

The `steipete/gogcli` repository provides a command-line interface for managing Gmail programmatically. Batch modifying Gmail labels with gogcli allows you to efficiently add or remove labels from hundreds of messages without exhausting API quotas through individual calls.

## How gogcli Implements Batch Label Modification

The batch modify functionality resides in the `internal/cmd` package and executes label changes through a single Gmail API request. This approach minimizes network overhead and respects Google's rate limits by bundling operations.

### Parsing Arguments and CSV Labels

In [`internal/cmd/gmail_batch.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_batch.go), the `GmailBatchModifyCmd` function handles argument parsing. It collects message IDs from positional arguments and processes the `--add` and `--remove` flags using the `splitCSV` helper defined in [`internal/cmd/csv.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/csv.go).

This design allows you to specify multiple labels as comma-separated values:

```bash
--add=Work,Urgent --remove=Spam,Promotions

```

### Resolving Label Names to IDs

Gmail's API requires label IDs, not display names. The `fetchLabelNameToID` function in [`internal/cmd/gmail_labels.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_labels.go) retrieves the complete label mapping, while `resolveLabelIDs` in [`internal/cmd/gmail_labels_utils.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_labels_utils.go) converts user-supplied names to their canonical IDs, falling back to raw values if no match exists.

This resolution happens automatically, allowing you to use familiar names like "Important" while the tool handles the underlying "Label_123" identifiers.

### Executing the BatchModify API Call

Once resolved, the command invokes `svc.Users.Messages.BatchModify` at lines 119-128 of [`internal/cmd/gmail_batch.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_batch.go). This sends a single HTTP request to Gmail's `batchModify` endpoint containing all message IDs and label modifications.

The operation is atomic from the client's perspective—either all specified modifications succeed in one network round-trip, or the command reports the failure.

### Dry-Run and Output Formatting

The implementation respects the global `--dry-run` flag via the `dryRunExit` helper, echoing the intended operation without modifying data. Output formatting is handled conditionally: when `--json` is specified, the command emits structured JSON via `WriteJSON` in [`internal/outfmt/outfmt.go`](https://github.com/steipete/gogcli/blob/main/internal/outfmt/outfmt.go); otherwise, it prints a human-friendly count of affected messages.

## Practical Code Examples

### Basic Batch Modification

Add the "Important" label and remove the "Newsletters" label from three specific messages:

```bash
gog gmail batch modify \
    --add=Important \
    --remove=Newsletters \
    17c2a3f4b5d6e7f8 23d5b7c9e1a2f3g4 9a8b7c6d5e4f3g2h1

```

The `--add` and `--remove` flags accept comma-separated lists or single values. The command resolves "Important" and "Newsletters" to their Gmail IDs before sending the batch request.

### Using Label IDs Directly

When you already know the internal label IDs, specify them to skip the name lookup step:

```bash
gog gmail batch modify \
    --add=Label_123456 \
    --remove=Label_987654 \
    $(cat ids.txt)

```

This approach avoids the extra `Labels.List` API call, making it faster for bulk operations where you have cached the label mappings.

### Dry-Run to Preview Operations

Verify your command before executing it:

```bash
gog --dry-run gmail batch modify \
    --add=Work,Personal \
    --remove=Spam \
    1a2b3c4d5e6f7g8h9i0j

```

Output when using `--json`:

```json
{
  "message_ids": ["1a2b3c4d5e6f7g8h9i0j"],
  "add": ["Work","Personal"],
  "remove": ["Spam"]
}

```

No changes are sent to Gmail; this helps verify CSV parsing and label resolution.

### Mixing Names and IDs

You can combine human-readable names with raw label IDs in the same command:

```bash
gog gmail batch modify \
    --add=Label_123456,ProjectX \
    --remove=Inbox \
    5f6e7d8c9b0a1b2c3d4e

```

`ProjectX` is resolved to its ID, while `Label_123456` is used unchanged.

## Key Source Files and Functions

| File | Role | Relevant Sections |
|------|------|-------------------|
| [`internal/cmd/gmail_batch.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_batch.go) | Implements the **batch-modify** and **batch-delete** commands. | `GmailBatchModifyCmd.Run` (label parsing, API call) |
| [`internal/cmd/csv.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/csv.go) | Utility for splitting CSV strings into slices. | `splitCSV` |
| [`internal/cmd/gmail_labels_utils.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_labels_utils.go) | Helper for converting human-readable label names to Gmail IDs. | `resolveLabelIDs` |
| [`internal/cmd/gmail_labels.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_labels.go) | Retrieves the full label map from Gmail (ID ↔︎ name). | `fetchLabelNameToID` |
| [`internal/outfmt/outfmt.go`](https://github.com/steipete/gogcli/blob/main/internal/outfmt/outfmt.go) | Formats command output (JSON vs. plain text). | `IsJSON`, `WriteJSON` |
| [`internal/ui/ui.go`](https://github.com/steipete/gogcli/blob/main/internal/ui/ui.go) | Handles UI printing and error handling. | `ui.FromContext` calls used throughout |

These files provide the end-to-end flow: CLI flag parsing → CSV splitting → label resolution → batch API request → formatted output.

## Summary

- **Single API call efficiency**: The `gog gmail batch modify` command bundles multiple label operations into one `BatchModify` request, conserving Gmail API quota.
- **Automatic label resolution**: Human-readable names are automatically converted to Gmail label IDs via `fetchLabelNameToID` and `resolveLabelIDs`, though raw IDs are also accepted.
- **Safety features**: The global `--dry-run` flag lets you preview changes without executing them, while `--json` provides machine-readable output.
- **Flexible input**: Labels can be specified as comma-separated values, and message IDs can be passed as arguments or piped from files.

## Frequently Asked Questions

### Can I use label names instead of IDs with gogcli batch modify?

Yes, gogcli accepts human-readable label names via the `--add` and `--remove` flags. The `resolveLabelIDs` function in [`internal/cmd/gmail_labels_utils.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_labels_utils.go) automatically converts these names to Gmail's internal label IDs before making the API call, falling back to the raw value if no match is found.

### Does gogcli support dry-run mode for batch label operations?

Yes, the global `--dry-run` flag is fully supported by the batch modify command. When enabled, the `dryRunExit` helper in [`internal/cmd/gmail_batch.go`](https://github.com/steipete/gogcli/blob/main/internal/cmd/gmail_batch.go) prints the intended operation as JSON or plain text without actually calling the Gmail API, allowing you to verify your command before execution.

### How many messages can I modify in a single batch command?

While gogcli itself does not enforce a hard limit, the underlying Gmail API typically allows batch operations on hundreds of messages per request. You should pass all target message IDs as positional arguments to the `gog gmail batch modify` command, and the tool will process them in a single `BatchModify` API call.

### What happens if a label name cannot be resolved?

If a label name cannot be resolved to an existing ID, the `resolveLabelIDs` function falls back to using the raw value provided. This allows you to specify label IDs directly, but means typos in label names may result in API errors rather than client-side validation failures.