Iteration Layer Sheet Generation vs OpenPyXL: Python Library or Language-Agnostic API?

7 min read Sheet Generation

OpenPyXL Does the Job. Eventually.

OpenPyXL is the default choice for generating Excel files in Python. It’s open source, it’s well-documented, and it supports styles, formulas, charts, and merged cells. If you write Python and need an XLSX file, you’ve probably used it or considered it.

The problem isn’t capability. OpenPyXL can do most of what you need. The problem is how many lines of code it takes to get there. Styling a cell requires creating a Font object, a PatternFill object, an Alignment object, and a Border object — then assigning each one to the cell. A formatted revenue report that takes 15 lines of JSON takes 60+ lines of OpenPyXL.

And if your team isn’t all-Python, OpenPyXL isn’t even an option.

The Verbosity Tax

Here’s what a simple styled spreadsheet looks like in OpenPyXL:

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, numbers

workbook = Workbook()
worksheet = workbook.active
worksheet.title = "Q1 Revenue"

header_font = Font(bold=True, color="FFFFFF", size=12)
header_fill = PatternFill(start_color="1A1A2E", end_color="1A1A2E", fill_type="solid")
header_alignment = Alignment(horizontal="center")

headers = ["Company", "Region", "Revenue"]
for col_index, header in enumerate(headers, 1):
    cell = worksheet.cell(row=1, column=col_index, value=header)
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = header_alignment

data = [
    ["Acme Corp", "North America", 48500.00],
    ["Globex Ltd", "Europe", 37200.00],
]
for row_index, row in enumerate(data, 2):
    for col_index, value in enumerate(row, 1):
        cell = worksheet.cell(row=row_index, column=col_index, value=value)
        if col_index == 3:
            cell.number_format = numbers.FORMAT_CURRENCY_USD_SIMPLE

workbook.save("report.xlsx")

That’s 25 lines for a 2-row, 3-column spreadsheet with bold headers and currency formatting. Every style property is a separate object. Every cell is addressed by row and column index. The header styling loop creates and assigns three objects per cell.

Now add merged cells, formulas, multiple sheets, conditional formatting, and column widths. The code grows linearly with every feature — there’s no way to declare a base style and have it apply everywhere. You style cells one at a time.

What OpenPyXL Doesn’t Do

OpenPyXL is focused on one thing: XLSX files. That focus is both its strength and its limitation.

  • No CSV output. If you need the same data as CSV, you write separate code using Python’s csv module. Two code paths for the same data, maintained separately.
  • No Markdown output. Need a Markdown table for documentation or a README? That’s a third code path.
  • No formula evaluation. OpenPyXL writes formulas into cells, but doesn’t evaluate them. If you export to CSV (using separate code), formulas can’t follow — you’d need to compute the values yourself.
  • Python only. Your Go service, your Node.js frontend, your Java batch processor — none of them can use OpenPyXL. You either add a Python sidecar to your infrastructure or find a different library for each language.
  • No API access. OpenPyXL runs in your process. If you need spreadsheet generation from a mobile app, a serverless function with size constraints, or a language that isn’t Python, you need to build and host a Python service yourself.

A Direct Comparison

Here’s the same task in both approaches — a formatted revenue report with styled headers and currency values.

OpenPyXL (Python only, you host this):

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, numbers

workbook = Workbook()
worksheet = workbook.active
worksheet.title = "Q1 Revenue"

header_font = Font(bold=True, color="FFFFFF", size=12)
header_fill = PatternFill(start_color="1A1A2E", end_color="1A1A2E", fill_type="solid")
header_alignment = Alignment(horizontal="center")

for col_index, header in enumerate(["Company", "Region", "Revenue"], 1):
    cell = worksheet.cell(row=1, column=col_index, value=header)
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = header_alignment

worksheet.cell(row=2, column=1, value="Acme Corp")
worksheet.cell(row=2, column=2, value="North America")
revenue_cell = worksheet.cell(row=2, column=3, value=48500.00)
revenue_cell.number_format = numbers.FORMAT_CURRENCY_USD_SIMPLE

worksheet.cell(row=3, column=1, value="Globex Ltd")
worksheet.cell(row=3, column=2, value="Europe")
revenue_cell_2 = worksheet.cell(row=3, column=3, value=37200.00)
revenue_cell_2.number_format = numbers.FORMAT_CURRENCY_USD_SIMPLE

workbook.save("report.xlsx")

Iteration Layer (any language, formatting included):

Request
curl -X POST https://api.iterationlayer.com/sheet-generation/v1/generate \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "format": "xlsx",
    "styles": {
      "header": {
        "is_bold": true,
        "font_size_in_pt": 12,
        "background_color": "#1a1a2e",
        "font_color": "#ffffff",
        "horizontal_alignment": "center"
      }
    },
    "sheets": [
      {
        "name": "Q1 Revenue",
        "columns": [
          {
            "name": "Company"
          },
          {
            "name": "Region"
          },
          {
            "name": "Revenue"
          }
        ],
        "rows": [
          [
            {
              "value": "Acme Corp"
            },
            {
              "value": "North America"
            },
            {
              "value": 48500.00,
              "format": "currency",
              "currency_code": "USD"
            }
          ],
          [
            {
              "value": "Globex Ltd"
            },
            {
              "value": "Europe"
            },
            {
              "value": 37200.00,
              "format": "currency",
              "currency_code": "EUR"
            }
          ]
        ]
      }
    ]
  }'
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({
  apiKey: "YOUR_API_KEY",
});

const result = await client.generateSheet({
  format: "xlsx",
  styles: {
    header: {
      is_bold: true,
      font_size_in_pt: 12,
      background_color: "#1a1a2e",
      font_color: "#ffffff",
      horizontal_alignment: "center",
    },
  },
  sheets: [
    {
      name: "Q1 Revenue",
      columns: [
        {
          name: "Company",
        },
        {
          name: "Region",
        },
        {
          name: "Revenue",
        },
      ],
      rows: [
        [
          {
            value: "Acme Corp",
          },
          {
            value: "North America",
          },
          {
            value: 48500.00,
            format: "currency",
            currency_code: "USD",
          },
        ],
        [
          {
            value: "Globex Ltd",
          },
          {
            value: "Europe",
          },
          {
            value: 37200.00,
            format: "currency",
            currency_code: "EUR",
          },
        ],
      ],
    },
  ],
});
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")

result = client.generate_sheet(
    format="xlsx",
    styles={
        "header": {
            "is_bold": True,
            "font_size_in_pt": 12,
            "background_color": "#1a1a2e",
            "font_color": "#ffffff",
            "horizontal_alignment": "center",
        },
    },
    sheets=[
        {
            "name": "Q1 Revenue",
            "columns": [
                {
                    "name": "Company",
                },
                {
                    "name": "Region",
                },
                {
                    "name": "Revenue",
                },
            ],
            "rows": [
                [
                    {
                        "value": "Acme Corp",
                    },
                    {
                        "value": "North America",
                    },
                    {
                        "value": 48500.00,
                        "format": "currency",
                        "currency_code": "USD",
                    },
                ],
                [
                    {
                        "value": "Globex Ltd",
                    },
                    {
                        "value": "Europe",
                    },
                    {
                        "value": 37200.00,
                        "format": "currency",
                        "currency_code": "EUR",
                    },
                ],
            ],
        },
    ],
)
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
package main

import il "github.com/iterationlayer/sdk-go"

func main() {
    client := il.NewClient("YOUR_API_KEY")

    result, err := client.GenerateSheet(il.GenerateSheetRequest{
        Format: "xlsx",
        Styles: &il.SheetStyles{
            Header: &il.CellStyle{
                IsBold:              true,
                FontSizeInPt:        12,
                BackgroundColor:     "#1a1a2e",
                FontColor:           "#ffffff",
                HorizontalAlignment: "center",
            },
        },
        Sheets: []il.Sheet{
            {
                Name: "Q1 Revenue",
                Columns: []il.SheetColumn{
                    {
                        Name: "Company",
                    },
                    {
                        Name: "Region",
                    },
                    {
                        Name: "Revenue",
                    },
                },
                Rows: []il.SheetRow{
                    {
                        {
                            Value: "Acme Corp",
                        },
                        {
                            Value: "North America",
                        },
                        {
                            Value:        48500.00,
                            Format:       "currency",
                            CurrencyCode: "USD",
                        },
                    },
                    {
                        {
                            Value: "Globex Ltd",
                        },
                        {
                            Value: "Europe",
                        },
                        {
                            Value:        37200.00,
                            Format:       "currency",
                            CurrencyCode: "EUR",
                        },
                    },
                },
            },
        },
    })
    if err != nil {
        panic(err)
    }
    _ = result
}
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}

The OpenPyXL version creates three style objects, loops through headers to apply them, then addresses every data cell by row and column index. The API version declares header styles once and describes the data as columns and rows. No index math, no style objects, no loops.

Change "format": "xlsx" to "format": "csv" and the same payload produces a CSV file. The API evaluates any formulas server-side and writes computed values. Try that with OpenPyXL — you’d rewrite the export logic from scratch using Python’s csv module.

When OpenPyXL Is the Right Choice

OpenPyXL has genuine strengths. Acknowledging them is more useful than pretending they don’t exist.

  • You need to read and modify existing spreadsheets. OpenPyXL can open an XLSX file, modify specific cells, and save it. The Sheet Generation API generates new files; it doesn’t edit existing ones.
  • You need charts or advanced Excel features. OpenPyXL supports charts, pivot tables, data validation, and conditional formatting. If your spreadsheet needs a bar chart embedded in it, OpenPyXL can do that.
  • Offline or air-gapped environments. If your data can’t leave your network, a cloud API isn’t an option.
  • Your entire stack is Python and you’re comfortable with the API. If your team knows OpenPyXL, your spreadsheet generation is stable, and you don’t need multi-format output, there’s no reason to change.

When an API Makes More Sense

For most teams building products, the trade-off favors an API call.

  • Your backend isn’t Python. The API works with any language. No Python sidecar, no polyglot dependency management.
  • You need multiple output formats. XLSX, CSV, and Markdown from the same JSON payload. One input, three formats. No separate code paths.
  • You want less code. Declaring a spreadsheet as JSON — columns, rows, styles — is less code than constructing Workbook, Font, PatternFill, and Alignment objects. Less code means fewer bugs and faster iteration.
  • You need formulas that work everywhere. The API evaluates =SUM(B2:B4) server-side for CSV and Markdown output. In XLSX, it’s a native Excel formula. Same input, correct output in every format.
  • You don’t want to manage a Python runtime for spreadsheet generation. If your main service is Go or Elixir or Rust, adding a Python dependency just for XLSX export is overhead you don’t need.

Get Started

Check the Sheet Generation docs for the full API reference — all cell formats, style properties, formula support, and cell merging. The TypeScript and Python SDKs handle authentication and response parsing.

Iteration Layer runs on EU infrastructure (Frankfurt), which matters if your data residency requirements rule out US-hosted services.

Start building in minutes

Free trial included. No credit card required.