qax-os/excelize · error

row %d has already been written

Error message

row %d has already been written

What it means

The streaming writer (StreamWriter) requires rows to be written in strictly ascending order because rows are serialized incrementally. SetRow was called with a row number that was already flushed to the XML stream, so the library refuses to write it a second time.

Source

Thrown at errors.go:387

	return fmt.Errorf("selected item %s does not exist in pivot table field %s", item, field)
}

// newPivotTableRangeError defined the error message on receiving the invalid
// pivot table range.
func newPivotTableRangeError(msg string) error {
	return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", msg)
}

// newPivotTableShowValuesAsBaseFieldError defined the error message on receiving
// the invalid pivot table "show values as" base field.
func newPivotTableShowValuesAsBaseFieldError(field string) error {
	return fmt.Errorf("base field %s does not exist in shared items", field)
}

// newStreamSetRowError defined the error message on the stream writer
// receiving the non-ascending row number.
func newStreamSetRowError(row int) error {
	return fmt.Errorf("row %d has already been written", row)
}

// newStreamSetRowOrderError defined the error message on calling the SetRow
// function before the order function.
func newStreamSetRowOrderError(name string) error {
	return fmt.Errorf("must call the %s function before the SetRow function", name)
}

// newUnknownFilterTokenError defined the error message on receiving a unknown
// filter operator token.
func newUnknownFilterTokenError(token string) error {
	return fmt.Errorf("unknown operator: %s", token)
}

// newUnsupportedChartType defined the error message on receiving the chart
// type are unsupported.
func newUnsupportedChartType(chartType ChartType) error {
	return fmt.Errorf("unsupported chart type %d", chartType)

View on GitHub (pinned to f2483381fb)

Solutions

  1. Ensure each SetRow call uses a monotonically increasing row number; track the last written row in your loop.
  2. Batch all cells for a row into one SetRow call instead of writing the row twice.
  3. Buffer rows in memory and sort/merge them before writing if your data source is not row-ordered.
  4. If you need random-access writing, use f.SetSheetRow on the regular File API instead of StreamWriter.

Example fix

// before
for i, v := range data {
    sw.SetRow("Sheet1", 0, excelize.Row{Cell: ...})
    sw.SetRow("Sheet1", i, row) // i==0 collides with header row 0
}
// after
sw.SetRow("Sheet1", 0, headerRow)
for i, v := range data {
    sw.SetRow("Sheet1", i+1, buildRow(v))
}
Defensive patterns

Strategy: validation

Validate before calling

lastRow := -1
writeRow := func(row int, cells excelize.Row) error {
    if row <= lastRow {
        return fmt.Errorf("stream row %d not greater than last written %d", row, lastRow)
    }
    lastRow = row
    return nil
}

Try / catch

if err := sw.SetRow(sheet, row, cells); err != nil {
    if strings.Contains(err.Error(), "has already been written") {
        log.Printf("skipping duplicate row %d (stream rows are write-once)", row)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling sw.SetRow(sheet, row, cellList) twice with the same row number, or calling SetRow with a row number less than the highest previously written row (e.g. writing row 5 then row 3).

Common situations: Off-by-one loops (row starting at 1 but SetRow already wrote row 1 as a header); re-writing a header after data rows; retry logic that replays a failed SetRow; merging code paths that both write the same row.

Related errors


AI-assisted analysis of qax-os/excelize@f2483381fb (2026-09-02). Data as JSON: /api/errors/7649d2a27b30ce90. Report an issue: GitHub.