DEV Community

Cover image for How to Pull Specific Rows or Ranges Out of an Excel Workbook as Structured Data
PDF4me
PDF4me

Posted on

How to Pull Specific Rows or Ranges Out of an Excel Workbook as Structured Data

How to Pull Specific Rows or Ranges Out of an Excel Workbook as Structured Data

Most of the time, nobody wants your whole spreadsheet. A finance team doesn't need the formatting, the pivot tables, or the three dead sheets nobody deleted. They want rows 2 through 500 of the "Transactions" tab, as JSON, so their own system can do something with it. That gap between "here is an Excel file" and "here is the data I actually need" is where a surprising amount of integration code gets written badly, because reading a specific slice of a worksheet programmatically is less trivial than it looks.

Why reading a worksheet is harder than opening it

Open Excel, select a range, copy it. Done, for a human. For code, that same task means parsing a binary or XML-based file format, resolving which sheet you mean when a workbook has twelve of them, deciding whether row one is a header or data, and handling the fact that spreadsheets are full of things that break naive row-counting: hidden rows, fully blank rows left over from a deleted filter, and cells that are visually empty but not actually null. None of that is specific to PDF4me. It is just what "extract rows from Excel" means once you take it seriously.

PDF4me's Office API handles Word and Excel automation over HTTPS, no Microsoft Office installation required on either end. For Excel specifically, that includes adding rows, updating rows in place, merging workbooks, securing and unlocking files, and the operation this article is about: extracting rows.

What Extract Rows actually does

The Extract Rows REST endpoint is deliberately narrow. It is a read-only call. Send it a workbook, and it hands back a slice of that workbook as structured JSON. It does not modify, resave, or return a new file.

You POST to https://api.pdf4me.com/office/ApiV2Excel/ExcelExtractRows with a Content-Type: application/json header and an Authorization: Basic header. The body carries the workbook's Base64-encoded content plus an extractRowsToExcelAction object. Here's the minimal shape, straight from PDF4me's own docs:

{
  "document": { "Name": "data.xlsx" },
  "docContent": "UEsDBBQABgAIAAAA...",
  "extractRowsToExcelAction": {}
}
Enter fullscreen mode Exit fullscreen mode

An empty extractRowsToExcelAction reads the default sheet in full. More usefully, it takes:

  • worksheetName or worksheetIndex, to target a specific sheet by name or by its zero-based position.
  • fromRow and toRow, the row range you want, also zero-based, where -1 means "through the last row that actually has data."
  • cultureName, which controls how dates and numbers get serialized. A workbook built in Germany and read with en-US culture can silently turn 1.234,56 into the wrong number, so this field matters more than it looks like it should.

Filled in, pulling a named sheet with the first 100 rows:

{
  "document": { "Name": "sales.xlsx" },
  "docContent": "UEsDBBQABgAIAAAA...",
  "extractRowsToExcelAction": {
    "worksheetName": "Q3",
    "fromRow": 0,
    "toRow": 99,
    "cultureName": "en-US"
  }
}
Enter fullscreen mode Exit fullscreen mode

The response comes back as a rowData array, one object per row, plus a success flag you should check before you touch rowData at all, and an errorMessage that gets populated on failure, things like a missing worksheet or a corrupted file:

{
  "document": null,
  "fileName": null,
  "success": true,
  "errorMessage": null,
  "rowData": [
    { "ColumnA": "value1", "ColumnB": "value2" },
    { "ColumnA": "value3", "ColumnB": "value4" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Row indexing being zero-based is a classic source of off-by-one bugs: "give me the first 100 rows" is fromRow: 0, toRow: 99, not 1 to 100.

Here's the same call in Python, using the request shape above:

import base64
import requests

with open("sales.xlsx", "rb") as f:
    doc_content = base64.b64encode(f.read()).decode("utf-8")

payload = {
    "document": {"Name": "sales.xlsx"},
    "docContent": doc_content,
    "extractRowsToExcelAction": {
        "worksheetName": "Q3",
        "fromRow": 0,
        "toRow": 99,
        "cultureName": "en-US"
    }
}

response = requests.post(
    "https://api.pdf4me.com/office/ApiV2Excel/ExcelExtractRows",
    json=payload,
    headers={
        "Content-Type": "application/json",
        "Authorization": "Basic YOUR_API_KEY"
    }
)

result = response.json()
if result["success"]:
    rows = result["rowData"]
else:
    print(result["errorMessage"])
Enter fullscreen mode Exit fullscreen mode

Headers, hidden rows, and the options the REST endpoint keeps minimal

The bare REST action is intentionally thin. The richer control over what counts as a "row" lives in PDF4me's integration-platform actions for this same operation, and they're worth knowing about even if you're calling the REST endpoint directly, because they show you what edge cases exist. The Power Automate action and the Make module both expose: treating the first row as column headers (or auto-naming columns Column1, Column2...), excluding hidden rows and hidden columns, excluding fully empty rows, and choosing whether to export every value as text or keep native types like numbers, booleans, and dates. That last option matters if you're piping the output straight into a system that's picky about type, a date that arrives as a string versus an ISO timestamp is a different bug report depending on which side of the integration you're debugging.

PDF4me's n8n action adds one more convenience worth calling out: it accepts the source file as binary data, a Base64 string, or a public URL, and writes its result, including a rowsExtracted count and the full settings it applied, into a binary slot your next node can read directly via $json.extractedData.rowData. That's a smaller detail, but it's the difference between wiring three nodes together cleanly and writing a Function node to reshape the output first.

The one inconsistency worth knowing about before you build on more than one platform

Here's something the docs don't call out directly, so it's worth being explicit: row and column numbering is not consistent across every PDF4me Extract Rows surface. The REST endpoint, the Power Automate action, the Make module, and the n8n node all use zero-based indexing, where -1 reaches the last row or column with data. PDF4me's Zapier action is the odd one out: its First Row, Last Row, First Column, and Last Column fields are explicitly documented as 1-based, "matching Excel," with -1 still meaning "through the last row or column with data." If you're moving the same automation logic from, say, Make to Zapier, that difference will cost you an off-by-one bug the first time you port a hardcoded range instead of re-deriving it. Treat that number as something to re-verify per platform, not something to copy-paste.

What this operation will not do for you

Extract Rows scopes by position: a starting row, an ending row, optionally a worksheet. None of the four platforms describe any way to filter by a cell's actual value, "give me only the rows where Status equals Overdue" is not something this endpoint does. If you need that, you pull the full range and filter in your own code or your automation platform's own filter step, treat Extract Rows as the data-fetching half of the job, not the querying half.

It's also worth separating this from two adjacent operations you'll bump into in the same product area. Extract Worksheets works one level up: instead of pulling specific rows from a sheet, it returns new workbooks containing only the whole tabs you select, by name or index. If what you actually want is "give me just the 'Q3' tab as its own file," that's Extract Worksheets, not Extract Rows. And if the workbook you're trying to read is password-protected, Extract Rows will simply fail against it. PDF4me's Unlock Document action removes file encryption and worksheet protection first, given the correct password. It can't recover a password you don't have, so this is a decryption step, not a bypass.

A realistic version of this: nightly report to API feed

Picture a finance system that drops a fresh .xlsx export into SharePoint or Google Drive every night at 2 a.m., a transaction log with a header row and anywhere from 40 to 4,000 data rows depending on the day. Manually opening that file to copy data into another system doesn't scale past about day three. Wired through Power Automate, Make, or n8n, the shape of the automation looks the same regardless of which platform you pick: a trigger fires when the new file lands, the Extract Rows action reads it with headers enabled and hidden rows excluded, and the resulting RowData array gets looped, with each row posted to an internal REST API or queued into a database insert. The entire "parse last night's spreadsheet" problem collapses into one action step, instead of a scheduled script somebody has to maintain and restart when it silently stops working.

Where to go from here

If you're calling this directly, start with the REST API reference and get the zero-based range math right before you add platform-specific options on top. If you're building this inside a no-code tool, the Power Automate, Make, Zapier, and n8n actions each wrap the same underlying call, with the row-numbering caveat above being the one detail that doesn't travel automatically between them. Either way, a worksheet that used to require someone opening Excel by hand becomes one HTTP call that returns exactly the rows you asked for and nothing else.

Website: pdf4me.com
Documentation: docs.pdf4me.com

Top comments (0)