Skip to content

Criar uma Planilha XLSX do Zero

O XlsxWriter constrói um arquivo Excel .xlsx do zero na memória e o grava em disco ou em um buffer de bytes — sem template, sem instalação do Excel, sem automação COM. É a ferramenta certa quando você quer controle total sobre abas, células, estilos, mesclagens e larguras de coluna, em vez de mapear através do IR agnóstico de formato.

O escritor faz parte do mesmo núcleo em Rust que extrai XLSX a 5,0ms de média com 100% de taxa de aprovação em arquivos Office válidos, e está disponível em Python, Rust, Go, C# e JavaScript (Node nativo).

Como criar um arquivo XLSX do zero?

Instancie um XlsxWriter, adicione uma aba, escreva células por (row, col) (ambos começando em 0) e chame save. Os valores das células podem ser str, int, float, bool ou None. O mesmo padrão funciona em todos os bindings.

Rust

use office_oxide::xlsx::write::{XlsxWriter, CellData, CellStyle, NumberFormat};

fn main() -> office_oxide::Result<()> {
    let mut wb = XlsxWriter::new();
    let mut sheet = wb.add_sheet("Sales");

    // Bold header row
    let header = CellStyle::new().bold().background("D3D3D3");
    sheet.set_cell_styled(0, 0, CellData::String("Item".into()),   header.clone());
    sheet.set_cell_styled(0, 1, CellData::String("Amount".into()), header);

    // Data rows
    sheet.set_cell(1, 0, CellData::String("Widget".into()));
    sheet.set_cell(1, 1, CellData::Number(1500.0));
    sheet.set_cell(2, 0, CellData::String("Gadget".into()));
    sheet.set_cell(2, 1, CellData::Number(2400.0));

    // SUM formula with currency formatting (omit the leading '=')
    let currency = CellStyle::new().number_format(NumberFormat::Currency);
    sheet.set_cell_styled(3, 1, CellData::Formula("SUM(B2:B3)".into()), currency);

    // Merge a title banner across two columns
    sheet.merge_cells(4, 0, 1, 2);

    sheet.set_column_width(0, 20.0);
    sheet.set_column_width(1, 15.0);

    wb.save("sales.xlsx")?;
    Ok(())
}

Python

from office_oxide import XlsxWriter

wb = XlsxWriter()
sheet = wb.add_sheet("Sales")          # -> 0 (the sheet index)

# Header row — bold, light-grey fill
wb.set_cell_styled(sheet, 0, 0, "Item",   bold=True, bg_color="D3D3D3")
wb.set_cell_styled(sheet, 0, 1, "Amount", bold=True, bg_color="D3D3D3")

# Data rows: row, col are 0-based
wb.set_cell(sheet, 1, 0, "Widget")
wb.set_cell(sheet, 1, 1, 1500.0)
wb.set_cell(sheet, 2, 0, "Gadget")
wb.set_cell(sheet, 2, 1, 2400.0)

# Widen the columns (Excel character units)
wb.set_column_width(sheet, 0, 20.0)
wb.set_column_width(sheet, 1, 15.0)

wb.save("sales.xlsx")

JavaScript

import { writeFileSync } from 'node:fs';
import { XlsxWriter } from 'office-oxide';

const wb = new XlsxWriter();
const sheet = wb.addSheet('Sales'); // 0-based index

// Header row — bold, grey fill (6-char hex string or null)
wb.setCellStyled(sheet, 0, 0, 'Item', true, 'D3D3D3');
wb.setCellStyled(sheet, 0, 1, 'Amount', true, 'D3D3D3');

// Data rows: value may be null, string, number, or boolean
wb.setCell(sheet, 1, 0, 'Widget');
wb.setCell(sheet, 1, 1, 1500.0);
wb.setCell(sheet, 2, 0, 'Gadget');
wb.setCell(sheet, 2, 1, 2400.0);

wb.mergeCells(sheet, 3, 0, 1, 2);   // title banner: 1 row x 2 cols
wb.setColumnWidth(sheet, 0, 20.0);
wb.setColumnWidth(sheet, 1, 15.0);

wb.save('sales.xlsx');

// Or export to a Buffer:
const data = wb.toBytes();
writeFileSync('sales-copy.xlsx', data);

wb.close(); // release the native handle

Go

package main

import (
	"os"

	officeoxide "github.com/yfedoseev/office_oxide/go"
)

func main() {
	wb := officeoxide.NewXlsxWriter()
	defer wb.Close()

	sheet := wb.AddSheet("Sales") // uint32, 0-based index

	// Header row — bold with a grey fill
	wb.SetCellStyled(sheet, 0, 0, "Item", true, "D3D3D3")
	wb.SetCellStyled(sheet, 0, 1, "Amount", true, "D3D3D3")

	// Data rows: value may be nil, string, float64, int, or bool
	wb.SetCell(sheet, 1, 0, "Widget")
	wb.SetCell(sheet, 1, 1, 1500.0)
	wb.SetCell(sheet, 2, 0, "Gadget")
	wb.SetCell(sheet, 2, 1, 2400.0)

	wb.MergeCells(sheet, 3, 0, 1, 2) // title banner: 1 row x 2 cols
	wb.SetColumnWidth(sheet, 0, 20.0)
	wb.SetColumnWidth(sheet, 1, 15.0)

	if err := wb.Save("sales.xlsx"); err != nil {
		panic(err)
	}

	// Or export to bytes:
	data, err := wb.ToBytes()
	if err != nil {
		panic(err)
	}
	_ = os.WriteFile("sales-copy.xlsx", data, 0o644)
}

C#

using OfficeOxide;

using var wb = new XlsxWriter();
uint sheet = wb.AddSheet("Sales"); // 0-based index

// Header row — bold, grey fill (6-char hex, no '#')
wb.SetCellStyled(sheet, 0, 0, "Item", bold: true, bgColor: "D3D3D3");
wb.SetCellStyled(sheet, 0, 1, "Amount", bold: true, bgColor: "D3D3D3");

// Data rows: value may be null, string, double, int, long, or bool
wb.SetCell(sheet, 1, 0, "Widget");
wb.SetCell(sheet, 1, 1, 1500.0);
wb.SetCell(sheet, 2, 0, "Gadget");
wb.SetCell(sheet, 2, 1, 2400.0);

wb.MergeCells(sheet, 3, 0, 1, 2);   // title banner: 1 row x 2 cols
wb.SetColumnWidth(sheet, 0, 20.0);
wb.SetColumnWidth(sheet, 1, 15.0);

wb.Save("sales.xlsx");

// Or export to a byte[]:
byte[] data = wb.ToBytes();
File.WriteAllBytes("sales-copy.xlsx", data);

C

OfficeXlsxWriterHandle *w = office_xlsx_writer_new();
uint32_t s = office_xlsx_writer_add_sheet(w, "Sales");   /* 0-based index */

/* value_type in the writer: EMPTY=0, STRING=1, NUMBER=2 (no BOOLEAN) */
/* header row — bold + 6-char hex bg ("D3D3D3") or NULL */
office_xlsx_sheet_set_cell_styled(w, s, 0, 0, OFFICE_CELL_STRING, "Item",   0.0, true, "D3D3D3");
office_xlsx_sheet_set_cell_styled(w, s, 0, 1, OFFICE_CELL_STRING, "Amount", 0.0, true, "D3D3D3");

/* data rows */
office_xlsx_sheet_set_cell(w, s, 1, 0, OFFICE_CELL_STRING, "Widget", 0.0);
office_xlsx_sheet_set_cell(w, s, 1, 1, OFFICE_CELL_NUMBER, NULL,     1500.0);
office_xlsx_sheet_set_cell(w, s, 2, 0, OFFICE_CELL_STRING, "Gadget", 0.0);
office_xlsx_sheet_set_cell(w, s, 2, 1, OFFICE_CELL_NUMBER, NULL,     2400.0);

office_xlsx_sheet_merge_cells(w, s, 3, 0, 1, 2);     /* title banner: row_span/col_span >= 1 */
office_xlsx_sheet_set_column_width(w, s, 0, 20.0);   /* Excel char units */
office_xlsx_sheet_set_column_width(w, s, 1, 15.0);

int err = 0;
office_xlsx_writer_save(w, "sales.xlsx", &err);
/* or: uint8_t *b = office_xlsx_writer_to_bytes(w, &out_len, &err); ... office_oxide_free_bytes */
office_xlsx_writer_free(w);

Para passar os bytes para uma resposta HTTP ou um armazenamento de objetos em vez de gravar em disco, use to_bytes() (exemplo em Python abaixo):

Python

data = wb.to_bytes()          # -> bytes (a complete .xlsx ZIP)
with open("sales.xlsx", "wb") as f:
    f.write(data)

Assinaturas dos métodos (Python)

Método Assinatura
Construtor XlsxWriter()
Adicionar aba add_sheet(name: str) -> int
Definir célula set_cell(sheet: int, row: int, col: int, value: None|str|bool|int|float) -> None
Definir célula estilizada set_cell_styled(sheet: int, row: int, col: int, value, bold: bool, bg_color: str|None = None) -> None
Mesclar merge_cells(sheet: int, row: int, col: int, row_span: int, col_span: int) -> None
Largura da coluna set_column_width(sheet: int, col: int, width: float) -> None
Salvar save(path) -> None
Exportar to_bytes() -> bytes

add_sheet retorna o índice baseado em 0 da nova aba — passe-o como o argumento sheet em todas as outras chamadas. Linhas e colunas começam em 0, portanto a célula A1 é (row=0, col=0) e B1 é (row=0, col=1).

Como mesclar células e criar um banner de título?

merge_cells(sheet, row, col, row_span, col_span) mescla um intervalo retangular ancorado em (row, col). Ambos os spans devem ser >= 1; o valor mesclado vem da célula âncora.

Python

from office_oxide import XlsxWriter

wb = XlsxWriter()
s = wb.add_sheet("Report")

# A title that spans columns A through C of the first row
wb.set_cell_styled(s, 0, 0, "Q3 Revenue Report", bold=True, bg_color="FFE699")
wb.merge_cells(s, 0, 0, 1, 3)          # 1 row tall, 3 columns wide

# Sub-header
wb.set_cell_styled(s, 1, 0, "Region", bold=True)
wb.set_cell_styled(s, 1, 1, "Q3",     bold=True)
wb.set_cell_styled(s, 1, 2, "QoQ %",  bold=True)

wb.set_cell(s, 2, 0, "NA")
wb.set_cell(s, 2, 1, 1_200_000)
wb.set_cell(s, 2, 2, 0.18)

wb.set_column_width(s, 0, 18.0)
wb.save("report.xlsx")

Assinaturas por linguagem

Rust — o crate Rust expõe um builder mais rico. add_sheet retorna um handle emprestado SheetData, e as células recebem um valor CellData (String, Number, Boolean, Formula ou Empty) mais um CellStyle opcional. CellStyle é um builder que suporta bold(), italic(), background(), number_format(), align() e mais. Principais assinaturas (em SheetData):

pub fn set_cell(&mut self, row: usize, col: usize, value: CellData) -> &mut Self
pub fn set_cell_styled(&mut self, row: usize, col: usize, value: CellData, style: CellStyle) -> &mut Self
pub fn merge_cells(&mut self, row: usize, col: usize, row_span: usize, col_span: usize) -> &mut Self
pub fn set_column_width(&mut self, col: usize, width: f64) -> &mut Self

e em XlsxWriter: add_sheet(name: &str) -> SheetData<'_>, save(path) -> Result<()> e write_to<W: Write + Seek>(writer) -> Result<()> para saída em memória. Um espelho baseado em índice (sheet_set_cell, sheet_set_cell_styled, sheet_merge_cells, sheet_set_column_width) também está disponível para quem prefere a mesma forma plana dos outros bindings.

PythonXlsxWriter(), add_sheet(name: str) -> int, set_cell(sheet: int, row: int, col: int, value: None|str|bool|int|float) -> None, set_cell_styled(sheet: int, row: int, col: int, value, bold: bool, bg_color: str|None = None) -> None, merge_cells(sheet: int, row: int, col: int, row_span: int, col_span: int) -> None, set_column_width(sheet: int, col: int, width: float) -> None, save(path) -> None, to_bytes() -> bytes.

JavaScriptaddSheet(name), setCell(sheet, row, col, value), setCellStyled(sheet, row, col, value, bold, bgColor = null), mergeCells(sheet, row, col, rowSpan, colSpan), setColumnWidth(sheet, col, width), save(path), toBytes(), close().

GoNewXlsxWriter() *XlsxWriter, AddSheet(name string) uint32, SetCell(sheet, row, col uint32, value any), SetCellStyled(sheet, row, col uint32, value any, bold bool, bgColor string), MergeCells(sheet, row, col, rowSpan, colSpan uint32), SetColumnWidth(sheet, col uint32, width float64), Save(path string) error, ToBytes() ([]byte, error). Passe "" como bgColor para sem preenchimento.

C#AddSheet(string name) -> uint, SetCell(uint sheet, uint row, uint col, object? value), SetCellStyled(uint sheet, uint row, uint col, object? value, bool bold, string? bgColor = null), MergeCells(uint sheet, uint row, uint col, uint rowSpan, uint colSpan), SetColumnWidth(uint sheet, uint col, double width), Save(string path), ToBytes() -> byte[]. XlsxWriter implementa IDisposable.

Coffice_xlsx_writer_new() -> OfficeXlsxWriterHandle*, office_xlsx_writer_add_sheet(w, name) -> uint32_t, office_xlsx_sheet_set_cell(w, sheet, row, col, value_type, value_str, value_num), office_xlsx_sheet_set_cell_styled(w, sheet, row, col, value_type, value_str, value_num, bold, bg_color), office_xlsx_sheet_merge_cells(w, sheet, row, col, row_span, col_span), office_xlsx_sheet_set_column_width(w, sheet, col, width), office_xlsx_writer_save(w, path, &err), office_xlsx_writer_to_bytes(w, &out_len, &err), office_xlsx_writer_free(w). value_type é OFFICE_CELL_EMPTY (0), OFFICE_CELL_STRING (1) ou OFFICE_CELL_NUMBER (2) — o writer não tem tipo de célula booleana.

Atenção: o XLSX-writer-from-scratch está disponível nos bindings nativos (Rust, Python, JavaScript/Node, Go, C#) e no C ABI. A compilação WASM para navegador é somente leitura — não há classes de writer — portanto construa planilhas com um dos itens acima e faça stream dos bytes.

Referência de estilos

Nos bindings Python, Go, C#, JavaScript e C, o setter estilizado expõe dois controles:

  • bold — um booleano que aplica negrito à célula.
  • bg_color / bgColor — uma string hexadecimal RGB de 6 caracteres sem # inicial (por exemplo, "D3D3D3" para cinza claro, "FFE699" para âmbar). Passe None / "" / null para sem preenchimento.

O builder CellStyle do Rust é a superfície completa — itálico, sublinhado, cor da fonte, tamanho da fonte, nome da fonte, formatos de número (General, Integer, Decimal2, Currency, Percent, Percent2, Date, DateTime), alinhamento horizontal e quebra de texto. Os outros bindings expõem as duas opções mais comuns diretamente; para estilos mais ricos nesses idiomas, construa via IR ou faça pós-processamento.

Perguntas frequentes

Linhas e colunas começam em 0 ou em 1? Tanto row quanto col são inteiros baseados em 0. A célula A1 é (row=0, col=0), B1 é (row=0, col=1) e A2 é (row=1, col=0). O índice de aba retornado por add_sheet também começa em 0.

Preciso instalar o Excel ou algum runtime Microsoft? Não. O XlsxWriter gera um .xlsx ZIP OOXML válido inteiramente em Rust. Não há automação COM, JVM nem dependência de sistema — o mesmo motor lê XLSX a 5,0ms de média com 100% de aprovação em arquivos Office válidos.

Como obter o arquivo como bytes em vez de gravar em disco? Chame to_bytes() (Python/C#/JS) ou ToBytes() (Go), que retorna um buffer completo de .xlsx. Em Rust use write_to(writer) com qualquer alvo Write + Seek, como um Cursor<Vec<u8>>. Ideal para respostas HTTP e uploads para armazenamento de objetos.

Posso escrever fórmulas? Sim, em Rust via CellData::Formula("SUM(B2:B3)") (omita o = inicial). Os bindings planos (Python/Go/C#/JS) atualmente suportam valores de células string, número, booleano e vazio via set_cell.

Como adicionar uma segunda planilha? Chame add_sheet novamente — cada chamada acrescenta uma aba e retorna o próximo índice baseado em 0. Passe esse índice como o argumento sheet nas chamadas subsequentes de set_cell / merge_cells / set_column_width.

Veja também