Back to blog

Bulk Import Limits: 13 Business Tools and What Still Gets Typed by Hand

Bulk import limits in QuickBooks, Xero, Salesforce, HubSpot and 9 more tools: file and row caps, what each refuses, and what still gets typed by hand.

Luka Abramovic17 min read

Isometric spreadsheet imports passing through a capacity-limited slot with rejected files set aside.

QuickBooks Online imports at most 100 invoices and 1,000 rows per file, and it refuses negative lines, so discounts and credit memos still get typed by hand (Intuit). Each invoice line takes its own row, so once your invoices average more than ten lines, the row cap bites before the invoice cap: 300 invoices of 24 lines need at least eight files, not three. Xero, by contrast, imports credit notes as negative amounts. Bulk import limits like these, and the refusal lists that come with them, decide which data entry survives an import.

Below are the import limits of 13 tools that established businesses load data into, grouped by kind: what each imports, the file and row caps, what it refuses, and what that means for the file you're preparing. Every limit links to the vendor's page and was checked in September 2026. We left out monday.com, Zoho CRM and Sage Intacct because we couldn't confirm their current limits in the vendors' own documentation. The last two sections are a pre-import checklist with formulas and two ways to split a file without splitting a record.

Accounting and ERP: QuickBooks Online, Xero and NetSuite

In accounting imports, structure bites before size. An invoice spans several rows, and a file cut between them produces two half-invoices or a rejected upload.

Accounting and ERP import limits, as of September 2026
ToolWhat it importsLimitsWhat it refusesWhat this means for your file
QuickBooks OnlineInvoices, one row per line item; bills; bank transactions from a file; products and servicesInvoices: 100 per file and 1,000 rows per file. Bills: Intuit recommends no more than 100 at a time, and in the US, bill import needs QuickBooks Online Advanced or above. Bank files: 350 KB or less, up to 1,000 lines, in English.Negative invoice lines, such as discounts or credit memos. Bill imports take no discounts or supplier credits either.Split between invoices, never inside one. Credit memos, supplier credits and discount lines are keyed after the import. On US Simple Start, Essentials and Plus, bills can't be imported at all, so they are keyed or captured another way.
XeroInvoices and customer credit notes, bills and supplier credit notes, bank statements in CSV, contacts and other lists, such as manual journals and inventory itemsInvoices, bills and credit notes: Xero recommends no more than 500 items per file. Contacts: split files over 1,000. Bank CSV: refused above 100,000 rows; split files over 1,000 transactions, and precoded statements into smaller batches, or the import can time out.Bank CSV files over 100,000 rows. In invoice files, account codes, tax types, tracking options, currencies and branding themes that don't match Xero's exactly: that detail isn't imported.Credit notes go in as negative unit amounts, so they needn't be typed. Give each bill and credit note its own reference: rows that share one are combined into a single bill, even across suppliers. Invoices, bills and credit notes arrive as drafts for someone to approve. Our default is to read "500 items" as 500 lines, and to cut bank data into 1,000-line files in date order, so a failed file is one date range.
NetSuiteMost common record types through the CSV Import Assistant, including multi-file imports that link sublist data, such as several addresses per customer25,000 records or 50 MB per import job; in a multi-file import, all files combinedRecord types missing from Oracle's supported list; for those, Oracle points to REST or SOAP web servicesSplit by the primary record and move its linked rows with it: a customer's addresses go in the same job and count toward the same 50 MB. Check the supported list before anyone promises a CSV migration of an unusual record type.

What the file route refuses, the API often accepts. The same vendors' API limits are in our API limits list.

How many QuickBooks invoice files you need

The lower bound is whichever cap bites first:

files needed >= MAX( ROUNDUP(invoices / 100, 0), ROUNDUP(invoice rows / 1000, 0) )
QuickBooks invoice files for two batches (illustrative inputs)
BatchInvoicesRowsFiles by the invoice capFiles by the row capFiles needed
Short invoices, 4 lines each1,4505,80015615
Long invoices, 24 lines each3007,200388

The break-even is ten lines per invoice. Above it, plan by rows: at 24 lines, one file holds 41 invoices (984 rows), so 300 invoices need eight files. Invoices of uneven length can need more files than the formula says, because an invoice can't be split. The splitting methods at the end pack them and tell you the real count. Our default is to cap files at 990 rows, so a header row or a stray blank line can't push a file over.

This week: export last quarter's invoices with line detail, invoice number in column A, and divide the rows by the invoices: =COUNTA(A2:A5000)/COUNTA(UNIQUE(FILTER(A2:A5000,A2:A5000<>""))). If the result is above ten, plan by rows. Then filter the amount column for values below zero. For QuickBooks, those lines are your manual list: decide now who keys them and which report proves they were keyed.

CRM: Salesforce, HubSpot and Pipedrive

CRM files rarely fail on size. They fail on which objects the import tool accepts, and on how you undo a load that mapped a column wrongly.

CRM import limits, as of September 2026
ToolWhat it importsLimitsWhat it refusesWhat this means for your file
Salesforce (Data Import Wizard)Accounts, contacts, leads, solutions, campaign members, person accounts and custom objects50,000 records at a time; files up to 100 MB; each record up to 400 KB and 90 fields; each note or description up to 32 KBAssets, cases, campaigns, contracts, documents, opportunities and productsThose objects need Data Loader or another API tool. On Professional Edition, API access is a paid add-on (see our CRM integration guide), so without it Salesforce's own tools offer no file route for them. Loading accounts or contacts that other users own needs the Modify All Data permission. Text past 32 KB is truncated, not rejected, so move it into an attached file before the load. Match re-imports on an External ID field so they update records instead of duplicating them.
HubSpotCRM records and activities, such as contacts, companies and notes, through the guided import tool or the imports APIThrough the imports API: 1,048,576 rows or 512 MB per file, whichever comes first, and 80 million rows a day, with a 429 error above either limit.Files over either capThe row cap equals Excel's sheet size, so any file Excel can hold passes it. Imports made through the API can't be picked when you filter records by import in views, lists, reports or workflows; guided-tool imports can. Run any load you may need to find and clean up later through the guided tool.
PipedriveSpreadsheets in XLSX or CSV50 MB and 50,000 rows per fileFiles over either capA bad import can be reverted from the import history tab within 48 hours. Check a sample of the imported records the same day, because after the window closes the cleanup is manual.

This week: list the objects in your next CRM load and check each one against the refusal column. In Salesforce, if opportunities or products are on the list, confirm that your edition has API access before anyone promises a file import, and if other reps own the records, that whoever runs the wizard has Modify All Data. In HubSpot, run one-off migrations through the guided tool, so the import stays filterable afterwards.

Work management and commerce: Jira, Smartsheet, Airtable and Shopify

These tools cap the container as well as the file. A sheet, a base or a store has a ceiling that an import can hit even when the file itself is small.

Work management and commerce import limits, as of September 2026
ToolWhat it importsLimitsWhat it refusesWhat this means for your file
Jira CloudWork items from CSV; in Jira Service Management, customers and organizationsWithout Jira admin permissions, 250 work items per CSV file. Service Management customer and organization imports need a Jira admin.More than 250 work items in one file, unless you're a Jira adminAbove 250, split into files of 250, or ask a Jira admin to run the file through Settings, System, External system import, CSV (the admin import).
SmartsheetFiles imported as new sheetsUp to 20,000 rows, 400 columns or 500,000 cellsFiles past any of the three caps. Excel formulas may not come through working.Past 25 columns, the cell cap arrives before the row cap: 40 columns allow 12,500 rows. The import creates a new sheet; for rows headed into an existing sheet, look at Data Shuttle upload workflows. Check every formula column after the import and rebuild what broke.
AirtableRecords into a base's tablesRecords per base: 1,000 on Free, 50,000 on Team, 125,000 on BusinessRecords past the plan's capThe cap covers every table in the base. Add the base's current record count to the file's rows; if the sum passes the cap, split the data across bases or change plan before importing.
ShopifyProducts from CSV, one row per variant15 MB per product CSV. A store with 50,000 or more variants can create only 1,000 new variants a day, CSV imports included; Shopify Plus stores are exempt.New variants past the daily capSplit on the Handle column, so every variant of a product stays in one file. Adding 10,000 variants to a store that already has 50,000 takes ten days, so start large catalog loads early.

This week: before the next load, write down the ceiling that applies to you: 250 per file if whoever runs your Jira imports isn't a Jira admin, 500,000 divided by your column count in Smartsheet, and the Airtable base's record count or the Shopify store's variant count against its cap.

Spreadsheets and lists: Excel, Google Sheets and SharePoint

Most import files pass through a spreadsheet on the way, and that's where IDs get damaged, before any vendor limit applies.

Spreadsheet and list limits that affect imports, as of September 2026
ToolWhat it importsLimitsWhat it refusesWhat this means for your file
ExcelCSV, text and workbook data1,048,576 rows by 16,384 columns per sheet; 15 digits of precision; 32,767 characters per cell (Excel specifications and limits)Digits past the 15th in a number, which become zeros; text past 32,767 characters in a cellOpening a CSV by double-clicking it can strip leading zeros and cut long numbers to 15 digits, so 00123 becomes 123 and a 16-digit card or account number ends in a zero. Excel for Windows and Mac let you switch those conversions off (on Windows, File, Options, Data); do it on every machine that prepares import files.
Google SheetsCSV and spreadsheet files20 million cells per spreadsheet, up from 10 million. Google announced general availability on September 10, 2026, for new, existing and imported files.Imports that would push the spreadsheet past the capPages quoting 10 million are out of date. Every tab counts toward the cap, so an 800,000-row export 26 columns wide (20.8 million cells) still won't fit in one spreadsheet.
SharePoint and Microsoft ListsA new list created from an Excel tableTables up to 5,000 rows. Past 5,000 items, views can hit the list view threshold, 5,000 items by default.Excel tables over 5,000 rows, when creating a listCreate the list from the first 5,000 rows, then load the rest in batches, as below. Before the list passes 5,000 items, set up views that filter first on an indexed column; our document management checklist explains why.

Load a SharePoint list past 5,000 rows

PnP PowerShell, the open-source SharePoint module, can add list items in batches. Since September 9, 2024 it has needed an Entra ID app registered in your own tenant, so ask your Microsoft 365 admin to create one with Register-PnPEntraIDAppForInteractiveLogin before the day of the load (PnP's registration guide). Add an ImportBatch text column to the list, then run:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/ops" -Interactive -ClientId "your-app-id"
$rows  = Import-Csv .\orders-remaining.csv
$batch = New-PnPBatch
$n = 0
foreach ($r in $rows) {
    Add-PnPListItem -List "Orders" -Batch $batch -Values @{
        "Title"       = $r.OrderNo
        "Customer"    = $r.Customer
        "Amount"      = $r.Amount
        "ImportBatch" = "2026-10-27-orders"
    }
    $n++
    if ($n % 500 -eq 0) { Invoke-PnPBatch -Batch $batch }
}
Invoke-PnPBatch -Batch $batch

Invoke-PnPBatch sends the queued items and clears the batch, so the loop can keep reusing it (PnP documentation). Sending every 500 rows is our default, so a failure affects a small group you can identify, and a view filtered on ImportBatch finds the whole load later. One trap is in PnP's own notes: inside a batch, number and currency values must match the site's regional format, while calls without a batch always take American notation. On a site set to German regional settings, 1234.56 has to arrive as 1234,56.

This week: open the last export someone reformatted in Excel before an import and run =SUMPRODUCT(--ISNUMBER(A2:A5000)) on an ID column that should keep leading zeros or runs past 15 digits. Anything above zero means Excel turned IDs into numbers. Switch off automatic conversions on that machine before the next file.

The pre-import checklist

Run these on the final file, in this order, before the first row goes in. If that file is a CSV, open it with automatic conversions switched off, or opening it will change the IDs you're checking. The formulas assume the record key (invoice number, email address, SKU) is in column A, rows 2 to 5000; adjust the ranges and columns to your file. Formulas with UNIQUE, FILTER or XMATCH need Microsoft 365 or Excel 2021; in older versions, enter the other array formulas with Ctrl+Shift+Enter.

Pre-import checklist with a formula for each check
CheckHowWhat it prevents
1. The largest record fits one file=MAX((A2:A5000<>"")*COUNTIF(A2:A5000,A2:A5000)) must not exceed the row capAn invoice with more lines than a file allows, which no split can fix
2. Keys are unique where they must be=SUMPRODUCT((A2:A5000<>"")*(COUNTIF(A2:A5000,A2:A5000)>1)) returns 0 for customer, contact or product files. Skip it for line-level files, where repeats are expected.Duplicate records, or one row overwriting another on update
3. IDs survived the spreadsheet=SUMPRODUCT(--ISNUMBER(B2:B5000)) returns 0 on any ID column with leading zeros or more than 15 digits00123 arriving as 123; a 16-digit card or account number arriving with a zero as its last digit
4. No stray spaces in keys and names=SUMPRODUCT(--(LEN(A2:A5000)<>LEN(TRIM(SUBSTITUTE(A2:A5000,UNICHAR(160)," "))))) returns 0; UNICHAR(160) catches the non-breaking spaces web exports carry"Acme Ltd " failing to match "Acme Ltd" and creating a second customer
5. Lookup values exist in the targetExport the target's list to a sheet named Existing, then =SUMPRODUCT((C2:C5000<>"")*ISNA(XMATCH(C2:C5000,Existing!A2:A5000))) returns 0. XMATCH's default exact match doesn't treat * and ? as wildcards, which COUNTIF and MATCH do.Invoices for customers the tool can't find, or new customers created from typos
6. Refused lines are listed, not lost=COUNTIF(D2:D5000,"<0") on the amount column. If the tool refuses negative lines, as QuickBooks does, move those rows to a sheet named Manual with an owner.Discount and credit lines quietly deleted by whoever "fixed" the file
7. Dates are dates=SUMPRODUCT(--ISTEXT(E2:E5000)) returns 0 on each date columnDates Excel didn't recognize, which importers reject or read with day and month swapped
8. Control totals recordedPer file, note rows (=COUNTA(A2:A5000)), records (=COUNTA(UNIQUE(FILTER(A2:A5000,A2:A5000<>"")))) and the amount total (=SUM(D2:D5000)). The script below writes them to a manifest.A load you can't prove: the tool's own report must show the same counts and totals afterwards
9. File size is under the capCheck the saved file's size against the tool: 350 KB for QuickBooks bank files, 15 MB for Shopify products, 50 MB for Pipedrive, 50 MB for all files in a NetSuite job together, 100 MB for the Salesforce wizardAn upload that fails at the last step
10. You can find the batch againAdd a column such as Import batch, filled with 2026-10-27-invoices-01, mapped to a field you can filter on. HubSpot's guided imports and Pipedrive's 48-hour revert give you this built in.Unpicking 4,000 records by hand when a mapping error shows up after the load
11. A five-record trialImport the five hardest records first: the longest, one with a zero or negative line, one with accented or Arabic characters (saved as CSV UTF-8), one with the most fields filled, and one that updates an existing record. Check each on screen.Finding a mapping or encoding error after the full load
12. A real error rate afterwardsCompare 50 imported records with the source file, using the method in our manual data entry guideAn import that "worked" but put values in the wrong fields

Split a file without splitting a record

Cutting a file every 1,000 rows is easy. The trap is a record that spans rows, such as an invoice with its lines or a Shopify product with its variants: a cut through the middle creates two half-records or a rejected file. Both methods below keep each record whole and respect a row cap and a record cap at the same time.

In Excel, with helper columns

Sort the data by the record key in column A. With data in columns A to M, add helper columns N to R. Fill N from row 2 down. In row 2, type TRUE in O2 and 1 in P2, Q2 and R2. Put the other formulas in row 3 and fill them down, with your caps in place of 1000 and 100:

N2  rows in this record:  =COUNTIF($A$2:$A$20000,A2)
O3  starts a new file:    =AND(A3<>A2, OR(Q2+N3>1000, R2+1>100))
P3  file number:          =P2+O3
Q3  rows in the file:     =IF(O3,1,Q2+1)
R3  records in the file:  =IF(O3,1,R2+(A3<>A2))

Filter column P to 1, copy columns A to M of the visible rows under a copy of the header row in a new sheet, and save it as CSV. Repeat for 2, 3 and so on. If =MAX(N2:N20000) is above the row cap, one record can't fit in any file, and that record needs splitting at the source or keying by hand.

With a script, for repeated loads

This Python script, standard library only, does the same packing, adds a byte cap for tools with a file-size limit, and writes a manifest with each file's records, rows and amount total. Those are your control totals for check 8.

#!/usr/bin/env python3
"""Split a CSV for a bulk import without splitting a record across files."""
import argparse, csv, io, sys
from decimal import Decimal, InvalidOperation

p = argparse.ArgumentParser()
p.add_argument("file")
p.add_argument("--key", help="column that identifies one record, e.g. InvoiceNo or Handle")
p.add_argument("--max-rows", type=int, required=True, help="data rows per file")
p.add_argument("--max-records", type=int, help="records per file, e.g. 100 invoices")
p.add_argument("--max-bytes", type=int, help="file size cap, e.g. 350000 for 350 KB")
p.add_argument("--amount", help="column to total per file, for control totals")
a = p.parse_args()

def size(row):  # bytes this row takes in the output file
    buf = io.StringIO()
    csv.writer(buf, lineterminator="\r\n").writerow(row)
    return len(buf.getvalue().encode("utf-8"))

with open(a.file, newline="", encoding="utf-8-sig") as f:
    reader = csv.reader(f)
    header = next(reader)
    rows = [r for r in reader if any(c.strip() for c in r)]

key = header.index(a.key) if a.key else None
amt = header.index(a.amount) if a.amount else None
records = {}  # rows of one record stay together, in order of first appearance
for n, row in enumerate(rows):
    records.setdefault(row[key].strip() if key is not None else n, []).append(row)

files, part, part_rows, part_bytes = [], [], 0, size(header)
for rec_id, rec in records.items():
    rec_rows, rec_bytes = len(rec), sum(size(r) for r in rec)
    if rec_rows > a.max_rows or (a.max_bytes and size(header) + rec_bytes > a.max_bytes):
        sys.exit(f"Record {rec_id!r} alone breaks a limit: {rec_rows} rows, {rec_bytes} bytes")
    full = (part_rows + rec_rows > a.max_rows
            or (a.max_records and len(part) == a.max_records)
            or (a.max_bytes and part_bytes + rec_bytes > a.max_bytes))
    if part and full:
        files.append(part)
        part, part_rows, part_bytes = [], 0, size(header)
    part.append(rec)
    part_rows += rec_rows
    part_bytes += rec_bytes
if part:
    files.append(part)

def total(part_rows):
    try:
        return sum(Decimal(r[amt].strip() or "0") for r in part_rows)
    except InvalidOperation:
        sys.exit(f"{a.amount} must hold plain numbers such as 1234.56")

stem = a.file.rsplit(".", 1)[0]
with open(f"{stem}-manifest.csv", "w", newline="", encoding="utf-8") as m:
    out = csv.writer(m)
    out.writerow(["file", "records", "rows", "amount_total"])
    for i, part in enumerate(files, 1):
        name = f"{stem}-part{i:02d}.csv"
        part_rows = [r for rec in part for r in rec]
        with open(name, "w", newline="", encoding="utf-8") as f:
            w = csv.writer(f, lineterminator="\r\n")
            w.writerow(header)
            w.writerows(part_rows)
        out.writerow([name, len(part), len(part_rows), total(part_rows) if amt is not None else ""])
print(f"{len(rows)} rows, {len(records)} records -> {len(files)} files. Totals in {stem}-manifest.csv")

Settings for the tools in this list, with our 990-row margin for QuickBooks and Xero's "500 items" read as 500 lines. The column names are examples; use your file's headers:

QuickBooks invoices:   python split_import.py invoices.csv --key InvoiceNo --max-rows 990 --max-records 100 --amount Amount
QuickBooks bank file:  python split_import.py bank.csv --max-rows 990 --max-bytes 350000 --amount Amount
Xero invoices, bills:  python split_import.py bills.csv --key InvoiceNumber --max-rows 500
Xero bank statement:   python split_import.py statement.csv --max-rows 1000 --amount Amount
Shopify products:      python split_import.py products.csv --key Handle --max-rows 999999 --max-bytes 15000000
Jira, without admin:   python split_import.py work-items.csv --max-rows 250

Import the files in order. After each one, compare the tool's count and total with that file's line in the manifest before starting the next, so a mismatch points at one file rather than the whole load. The script stops with the record's key if a single record can't fit under the caps, which is the same case as check 1.

If a file you import every week or month still needs splitting, fixing and retyping afterwards, bring one real file, the tool it goes into and the list of lines someone still keys by hand. We'd start with what the file route refuses, such as credit memos, opportunities or rows past a cap, and check whether the tool's API accepts them, so the import stops needing a person. That's where our systems integration work starts.

Build with Adamant Code

Work through one recurring handoff

Bring the systems involved, one recent example and the exceptions that cause rework. We will discuss what can be connected and where a person needs to stay in control.

Explore automation and integrations
Talk through your workflow