⚠️ Experimental Feature: The Builder API is experimental and may change in future releases. While functional and tested, the API design may evolve based on user feedback. Use with caution in production code and expect potential breaking changes.
The Builder API provides a high-level, simplified interface for creating Excel files without manually tracking cell positions. It's perfect for quickly generating reports and exporting data.
Use Builder when:
- Quickly exporting data from lists or database queries
- Generating simple reports with sequential data
- You don't need advanced features like formulas, images, or merged cells
- Speed of development is more important than fine-grained control
- Working with large datasets in a straightforward layout
Use Low-Level API when:
- Need precise control over cell positioning
- Using advanced features (formulas, images, merged cells, freeze panes)
- Creating complex layouts with scattered data
- Building spreadsheet templates with specific structure
The simplest possible example:
alias XlsxWriter.Builder
Builder.create()
|> Builder.add_sheet("Report")
|> Builder.add_rows([
["Name", "Value"],
["Alice", 100],
["Bob", 200]
])
|> Builder.write_file("report.xlsx")The Builder maintains state including:
- List of sheets with their instructions
- Current active sheet
- Cursor position (row, col)
- Column width mappings
All operations return the updated builder state for piping.
The cursor automatically advances as you add data:
Builder.create()
|> Builder.add_sheet("Data")
|> Builder.add_rows([["Row 1"]]) # Cursor at (0,0) → (1,0)
|> Builder.add_rows([["Row 2"]]) # Cursor at (1,0) → (2,0)
|> Builder.add_rows([["Row 3"]]) # Cursor at (2,0) → (3,0)Cell formatting uses the same syntax as XlsxWriter.write/5:
# Cell with formatting
{"Bold Text", format: [:bold]}
# Multiple format options
{"Styled", format: [:bold, :italic, {:font_size, 14}, {:bg_color, "#FFFF00"}]}
# Column width (Builder-specific)
{"Wide", format: [:bold], width: 30}Creates a new builder instance.
builder = Builder.create()Returns: A new builder state
Adds a new sheet and switches context to it. Resets cursor to (0, 0).
builder
|> Builder.add_sheet("Summary")
|> Builder.add_sheet("Details")Parameters:
builder- The builder statesheet_name- Name of the sheet (string)
Returns: Updated builder with new active sheet
Adds multiple rows starting at the current cursor position.
# Simple rows
builder |> Builder.add_rows([
["A", "B", "C"],
[1, 2, 3]
])
# With formatting
builder |> Builder.add_rows([
[{"Header", format: [:bold]}, {"Value", format: [:bold]}],
["Data", 100]
])
# Override position
builder |> Builder.add_rows(
[["Data"]],
start_row: 5,
start_col: 2
)Parameters:
builder- The builder staterows- List of rows (list of lists)opts- Options (optional)::start_row- Override cursor row (0-based):start_col- Override cursor column (0-based)
Returns: Updated builder with cursor moved after last row
Moves cursor down by N rows for spacing.
builder
|> Builder.add_rows([["Section 1"]])
|> Builder.skip_rows(2) # Add 2 blank rows
|> Builder.add_rows([["Section 2"]])Parameters:
builder- The builder staten- Number of rows to skip (default: 1)
Returns: Updated builder with cursor moved down
Generates the XLSX file as binary data.
{:ok, content} = Builder.write_binary(builder)
File.write!("output.xlsx", content)Returns: {:ok, binary} or {:error, reason}
Generates and writes the XLSX file to disk.
builder |> Builder.write_file("report.xlsx")Parameters:
builder- The builder statepath- Output file path (required)
Returns: :ok or {:error, reason}
All format options from XlsxWriter.write/5 are supported:
format: [:bold]
format: [:italic]
format: [:strikethrough]
format: [:bold, :italic] # Combine multipleformat: [{:font_size, 14}]
format: [{:font_color, "#FF0000"}] # Red
format: [{:font_name, "Arial"}]format: [{:bg_color, "#FFFF00"}] # Yellow background
format: [{:align, :left}]
format: [{:align, :center}]
format: [{:align, :right}]format: [{:num_format, "$#,##0.00"}] # Currency
format: [{:num_format, "0.00%"}] # Percentage
format: [{:num_format, "#,##0"}] # Thousandsformat: [{:border, :thin}]
format: [{:border_top, :thick}]
format: [{:border_bottom, :double}]
format: [{:border_left, :dashed}]
format: [{:border_right, :dotted}]Available border styles: :thin, :medium, :thick, :dashed, :dotted, :double, :hair, :medium_dashed, :dash_dot, :medium_dash_dot, :dash_dot_dot, :medium_dash_dot_dot, :slant_dash_dot
{"Text", width: 20} # Set column width
{"Text", format: [:bold], width: 30} # With formattingalias XlsxWriter.Builder
Builder.create()
|> Builder.add_sheet("Sales Report")
|> Builder.add_rows([
[{"Month", format: [:bold]}, {"Revenue", format: [:bold]}],
["January", {50000, format: [{:num_format, "$#,##0"}]}],
["February", {62000, format: [{:num_format, "$#,##0"}]}],
["March", {58000, format: [{:num_format, "$#,##0"}]}]
])
|> Builder.skip_rows(1)
|> Builder.add_rows([
[{"Total", format: [:bold]}, {170000, format: [{:num_format, "$#,##0"}]}]
])
|> Builder.write_file("sales_report.xlsx")Builder.create()
|> Builder.add_sheet("Summary")
|> Builder.add_rows([
[{"Quarterly Revenue", format: [:bold, {:font_size, 16}]}]
])
|> Builder.skip_rows(1)
|> Builder.add_rows([
[{"Quarter", format: [:bold]}, {"Amount", format: [:bold]}],
["Q1", 170000],
["Q2", 185000],
["Q3", 195000],
["Q4", 210000]
])
|> Builder.add_sheet("Q1 Details")
|> Builder.add_rows([
[
{"Product", format: [:bold], width: 25},
{"Units", format: [:bold], width: 12},
{"Revenue", format: [:bold], width: 15}
],
["Widget A", 1200, 36000],
["Widget B", 850, 42500]
])
|> Builder.write_file("quarterly_report.xlsx")# Generate 10,000 rows from database query
data = MyApp.Repo.all(User)
|> Enum.map(fn user ->
[user.name, user.email, user.created_at, user.status]
end)
Builder.create()
|> Builder.add_sheet("Users")
|> Builder.add_rows([
[
{"Name", format: [:bold], width: 20},
{"Email", format: [:bold], width: 30},
{"Created", format: [:bold], width: 15},
{"Status", format: [:bold], width: 10}
]
])
|> Builder.add_rows(data)
|> Builder.write_file("users_export.xlsx")Builder.create()
|> Builder.add_sheet("Status Report")
|> Builder.add_rows([
[{"Project Status Dashboard", format: [:bold, {:font_size, 18}, {:font_color, "#0066CC"}]}]
])
|> Builder.skip_rows(2)
|> Builder.add_rows([
[
{"Status", format: [:bold, {:bg_color, "#EEEEEE"}]},
{"Count", format: [:bold, {:bg_color, "#EEEEEE"}]},
{"Percentage", format: [:bold, {:bg_color, "#EEEEEE"}]}
],
[
{"Completed", format: [{:font_color, "#00AA00"}]},
42,
{0.52, format: [{:num_format, "0.0%"}]}
],
[
{"In Progress", format: [{:font_color, "#FF9900"}]},
28,
{0.35, format: [{:num_format, "0.0%"}]}
],
[
{"Pending", format: [{:font_color, "#0066CC"}]},
10,
{0.13, format: [{:num_format, "0.0%"}]}
]
])
|> Builder.skip_rows(1)
|> Builder.add_rows([
[
{"Total", format: [:bold, {:bg_color, "#FFFF99"}]},
{80, format: [:bold]},
{1.0, format: [:bold, {:num_format, "0.0%"}]}
]
])
|> Builder.write_file("status_report.xlsx")# Create dashboard-style layout with data in specific positions
Builder.create()
|> Builder.add_sheet("Dashboard")
# Top-left section
|> Builder.add_rows([
[{"Sales Metrics", format: [:bold, {:font_size, 14}]}]
], start_row: 0, start_col: 0)
|> Builder.add_rows([
[{"Metric", format: [:bold]}, {"Value", format: [:bold]}],
["Daily Sales", 15000],
["Monthly Sales", 450000]
], start_row: 2, start_col: 0)
# Top-right section
|> Builder.add_rows([
[{"Team Performance", format: [:bold, {:font_size, 14}]}]
], start_row: 0, start_col: 5)
|> Builder.add_rows([
[{"Team", format: [:bold]}, {"Target", format: [:bold]}],
["East", {125, format: [{:num_format, "0%"}]}],
["West", {98, format: [{:num_format, "0%"}]}]
], start_row: 2, start_col: 5)
|> Builder.write_file("dashboard.xlsx")- Batch your rows: Add multiple rows in one
add_rows/3call rather than multiple single-row calls - Use simple values: Plain values are faster than formatted tuples
- Minimize format changes: Group cells with similar formatting together
- Large datasets: The Builder handles 10,000+ rows efficiently
# Pattern: Bold headers + regular data
Builder.add_rows([
[{"Col1", format: [:bold]}, {"Col2", format: [:bold]}],
["Data1", "Data2"],
["Data3", "Data4"]
])# Pattern: Section header + spacing + data + spacing
builder
|> Builder.add_rows([[{"Section 1", format: [:bold]}]])
|> Builder.skip_rows(1)
|> Builder.add_rows(section1_data)
|> Builder.skip_rows(2)
|> Builder.add_rows([[{"Section 2", format: [:bold]}]])
|> Builder.skip_rows(1)
|> Builder.add_rows(section2_data)# Pattern: Data + blank row + totals
builder
|> Builder.add_rows(data_rows)
|> Builder.skip_rows(1)
|> Builder.add_rows([
[{"Total", format: [:bold]}, {sum, format: [:bold, {:num_format, "#,##0"}]}]
])# write_binary/1 returns result tuple
case Builder.write_binary(builder) do
{:ok, content} ->
File.write!("output.xlsx", content)
:ok
{:error, reason} ->
Logger.error("Failed to generate XLSX: #{reason}")
{:error, reason}
end
# write_file/2 also returns result
case Builder.write_file(builder, path) do
:ok ->
IO.puts("✓ File created: #{path}")
{:error, reason} ->
IO.puts("✗ Error: #{reason}")
endCommon errors:
"No sheets added"- Calladd_sheet/2before adding rows"No active sheet"- Add at least one sheet withadd_sheet/2
Run the included demo to see all features in action:
mix run examples/builder_demo.exsThis generates 5 example files demonstrating:
- Simple reports with automatic positioning
- Multi-sheet workbooks with formatting
- Large dataset generation (1000 rows)
- Complex formatting with colors and styles
- Positioned data with explicit coordinates
Builder.create()
|> Builder.add_sheet("Data")
|> Builder.add_rows([
[{"Name", format: [:bold], width: 20}, {"Age", format: [:bold]}],
["Alice", 30],
["Bob", 25]
])
|> Builder.write_file("output.xlsx")sheet = XlsxWriter.new_sheet("Data")
|> XlsxWriter.set_column_width(0, 20)
|> XlsxWriter.write(0, 0, "Name", format: [:bold])
|> XlsxWriter.write(0, 1, "Age", format: [:bold])
|> XlsxWriter.write(1, 0, "Alice")
|> XlsxWriter.write(1, 1, 30)
|> XlsxWriter.write(2, 0, "Bob")
|> XlsxWriter.write(2, 1, 25)
{:ok, content} = XlsxWriter.generate([sheet])
File.write!("output.xlsx", content)The Builder API is more concise for sequential data!
The Builder API currently does not support:
- Formulas (use low-level API)
- Images (use low-level API)
- Merged cells (use low-level API)
- Freeze panes (use low-level API)
- Autofilter (use low-level API)
- Hide rows/columns (use low-level API)
- Row height control (use low-level API)
For these features, use the low-level XlsxWriter API or consider mixing both:
# Use Builder for bulk data
builder = Builder.create()
|> Builder.add_sheet("Data")
|> Builder.add_rows(lots_of_data)
# Get the generated sheets
{:ok, binary} = Builder.write_binary(builder)
# Or build manually with low-level API for advanced features
sheet = XlsxWriter.new_sheet("Advanced")
|> XlsxWriter.write_formula(0, 0, "=SUM(A1:A10)")
|> XlsxWriter.freeze_panes(1, 0)
{:ok, content} = XlsxWriter.generate([sheet])Potential additions being considered:
skip_cols/2- Horizontal cursor movementmove_to/2- Absolute cursor positioning- Sheet-level options (freeze panes, autofilter)
add_row/2- Single row variant- Template-based generation
- Conditional formatting helpers
Feedback welcome! This API is experimental and we want your input.