# How Images Are Uploaded Programmatically in xiaohongshu-mcp: A Complete Technical Guide

> Learn how images are uploaded programmatically in xiaohongshu-mcp using the Rod headless browser. This guide details direct DOM file input injection and upload completion monitoring.

- Repository: [zy/xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp)
- Tags: how-to-guide
- Published: 2026-03-09

---

**The xiaohongshu-mcp project uses the Rod headless browser library to simulate user interactions with the Xiaohongshu web interface, injecting files directly into DOM file inputs and monitoring upload completion through preview element detection.**

Programmatically uploading images to Xiaohongshu presents unique challenges due to the platform's reliance on client-side JavaScript and anti-automation measures. The `xpzouying/xiaohongshu-mcp` repository solves this by leveraging browser automation rather than reverse-engineered APIs. This article examines the complete technical implementation found in the source code, detailing how the system validates, injects, and verifies image uploads through the web interface.

## The Core Upload Architecture

### Browser Automation with Rod

At the heart of the upload system lies the **Rod** library, a Go-based devtools driver that controls Chrome or Edge via the Chrome DevTools Protocol. Unlike simple HTTP clients, Rod operates a real browser instance, allowing `xiaohongshu-mcp` to interact with the Xiaohongshu web app's React-based upload widgets as a legitimate user would.

The browser configuration resides in [`configs/browser.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/configs/browser.go), where headless mode, proxy settings, and timeout values are defined. These settings directly impact upload reliability, particularly when handling large image files or slow network conditions.

### File Validation and Preparation

Before any browser interaction occurs, the system performs strict **path validation** in [`xiaohongshu/publish.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/xiaohongshu/publish.go). Each provided image path undergoes an `os.Stat` check. Files that do not exist are immediately skipped with a warning log entry, preventing the browser from encountering broken file references that could stall the upload queue.

Valid paths are collected into a slice and processed sequentially. This sequential approach ensures that the Xiaohongshu web interface receives files at a human-like pace, reducing the risk of rate limiting or upload corruption.

## Step-by-Step Upload Implementation in [`publish.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/publish.go)

The `uploadImages` function in [`xiaohongshu/publish.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/xiaohongshu/publish.go) orchestrates the entire process. It combines DOM manipulation with polling-based verification to ensure each image reaches Xiaohongshu's servers before proceeding.

### Path Validation and Filtering

The function begins by filtering the input slice:

```go
validPaths := []string{}
for _, p := range imagesPaths {
    if _, err := os.Stat(p); os.IsNotExist(err) {
        logrus.Warnf("图片文件不存在: %s", p)
        continue
    }
    validPaths = append(validPaths, p)
    logrus.Infof("获取有效图片：%s", p)
}

```

This defensive programming pattern ensures that only accessible files are passed to the browser automation layer.

### Sequential File Injection

Images upload one at a time. The function distinguishes between the first image and subsequent images using different CSS selectors:

- **First image**: Uses `.upload-input` (the visible "Add Image" button)
- **Subsequent images**: Uses `input[type="file"]` (the hidden file input that remains in the DOM)

```go
for i, path := range validPaths {
    selector := `input[type="file"]`
    if i == 0 {
        selector = ".upload-input"
    }

    uploadInput, err := page.Element(selector)
    if err != nil {
        return errors.Wrapf(err, "查找上传输入框失败(第%d张)", i+1)
    }
    if err := uploadInput.SetFiles([]string{path}); err != nil {
        return errors.Wrapf(err, "上传第%d张图片失败", i+1)
    }
    
    slog.Info("图片已提交上传", "index", i+1, "path", path)
    // ... wait for completion
}

```

The `Element.SetFiles` method is the critical Rod API call that injects the local file path into the browser's file input element, triggering the Xiaohongshu frontend's upload handler.

### Upload Completion Detection

Since browser-based uploads occur asynchronously, the system polls the DOM for visual confirmation. The `waitForUploadComplete` function monitors the appearance of preview thumbnails:

```go
func waitForUploadComplete(page *rod.Page, expectedCount int) error {
    maxWaitTime := 60 * time.Second
    checkInterval := 500 * time.Millisecond
    start := time.Now()
    lastLogCount := expectedCount - 1

    for time.Since(start) < maxWaitTime {
        uploadedImages, err := page.Elements(".img-preview-area .pr")
        if err != nil {
            time.Sleep(checkInterval)
            continue
        }
        currentCount := len(uploadedImages)

        if currentCount != lastLogCount {
            slog.Info("等待图片上传", "current", currentCount, "expected", expectedCount)
            lastLogCount = currentCount
        }
        if currentCount >= expectedCount {
            slog.Info("图片上传完成", "count", currentCount)
            return nil
        }
        time.Sleep(checkInterval)
    }
    return errors.Errorf("第%d张图片上传超时(60s)，请检查网络连接和图片大小", expectedCount)
}

```

This polling mechanism checks for the `.img-preview-area .pr` selector every 500 milliseconds, with a hard timeout of 60 seconds. The function returns successfully only when the number of preview elements matches the expected upload count, ensuring that subsequent publishing steps only execute after all images are fully processed by Xiaohongshu's servers.

## Integration with the Publish Workflow

The upload functionality integrates into the broader publishing pipeline through the `PublishAction` struct. When a user invokes the `Publish` method (defined in the service layer), the system executes the following sequence:

1. **Browser context initialization** – Ensures a logged-in Rod page is available
2. **Image upload phase** – Calls `uploadImages(page, content.ImagePaths)` as detailed above
3. **Content population** – Fills title, body text, and tags using DOM selectors
4. **Final submission** – Clicks the publish button after confirming all uploads completed

This architecture decouples the upload mechanism from content creation, allowing the same `uploadImages` function to be reused across different content types (standard posts, carousel posts, etc.).

## Summary

- **xiaohongshu-mcp** leverages the **Rod** headless browser library to automate image uploads through the official Xiaohongshu web interface rather than using undocumented APIs.
- The upload logic resides primarily in **[`xiaohongshu/publish.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/xiaohongshu/publish.go)**, specifically within the `uploadImages` and `waitForUploadComplete` functions.
- **Path validation** occurs before browser interaction, filtering out non-existent files to prevent automation failures.
- **Sequential upload** uses different CSS selectors for the first image (`.upload-input`) versus subsequent images (`input[type="file"]`), with `Element.SetFiles()` injecting file paths into the DOM.
- **Completion detection** relies on polling for preview thumbnail elements (`.img-preview-area .pr`) with a 60-second timeout, ensuring uploads finish before proceeding to publish.

## Frequently Asked Questions

### How does xiaohongshu-mcp handle missing or invalid image files?

The system performs strict pre-validation using `os.Stat` on each provided path before any browser interaction occurs. Files that do not exist are logged as warnings and skipped entirely, ensuring that only valid, accessible files are passed to the Rod browser's `SetFiles` method. This prevents the upload sequence from stalling due to broken file references.

### Why does the upload process use different CSS selectors for the first versus subsequent images?

Xiaohongshu's web interface presents different DOM states depending on whether any images have been uploaded yet. The first upload requires clicking the visible "Add Image" button (selector `.upload-input`), which reveals the hidden file input. After the first image is uploaded, the file input remains in the DOM and can be targeted directly with `input[type="file"]` for subsequent uploads. This selector differentiation ensures reliable element detection across the upload sequence.

### What happens if an image upload takes longer than 60 seconds?

The `waitForUploadComplete` function implements a hard timeout of 60 seconds (defined as `maxWaitTime := 60 * time.Second`). If the expected number of preview thumbnails (`.img-preview-area .pr`) does not appear within this window, the function returns an error wrapped with the message indicating which image timed out and suggesting checks for network connectivity or image file size. This error propagates up to halt the publishing process, preventing submission of incomplete posts.

### Can the upload mechanism handle video files as well?

While the primary `uploadImages` function in [`publish.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/publish.go) specifically handles image uploads, the repository contains a separate implementation in [`xiaohongshu/publish_video.go`](https://github.com/xpzouying/xiaohongshu-mcp/blob/main/xiaohongshu/publish_video.go) that follows a similar pattern for video content. Both implementations use Rod's `SetFiles` method and DOM polling strategies, but they target different upload widgets and completion indicators appropriate to their respective media types. The architecture allows for shared browser session management while keeping media-specific logic separated.