Mapping a QuickBooks or Xero Chart of Accounts into NetSuite

How to map a QuickBooks or Xero chart of accounts into NetSuite's structure, with a template and the naming conflicts that cause duplicate accounts.

SuiteMigration Team

September 18, 2026 · 15 min read

Migration

You have exported your QuickBooks or Xero chart of accounts. Fifty rows, maybe a hundred. Every row has a type and a name, NetSuite has the same types and takes names, and the mapping looks like it will fit in an afternoon.

The type and the name are the part that does. The rest of the NetSuite account form is fields the source system did not have, and NetSuite has an opinion about the default for each one. Subsidiary. Currency. Parent account. Number, once numbering is turned on.

Get those defaults wrong on the way in and the reports come out wrong on the way out. The import will not fail. The trial balance may even tie. The P&L is what gives it away, months later, when the same real-world expense is split across four accounts because nobody deduplicated the source names on the way in.

Get the names wrong and you find out sooner. The forty-odd variations of Office Supplies that surface between rows 200 and 400 of the source export, the ones that turn into 147 hours of VLOOKUPs when they are reconciled by hand, are why chart-of-accounts mapping is the first hard problem in a migration.

What follows is the source-to-destination template, the naming problem that turns the template into a duplicate-hunting exercise, the decisions that sit on top, and the errors NetSuite emits when a mapping is broken. It applies to a QuickBooks or Xero to NetSuite migration at any scale.

What NetSuite stores instead of a Type and a Number

The QuickBooks or Xero chart of accounts is a list of rows. Each row is a name, a type, an optional number, an optional description, and a parent-child relationship if you use hierarchy. NetSuite keeps those, and adds fields.

For a NetSuite OneWorld account with multiple currencies enabled, the required fields on a new account are:

  • Type. A fixed Oracle enum, in the order the New Account dropdown lists them: Bank, Accounts Receivable, Other Current Asset, Fixed Asset, Other Asset, Accounts Payable, Credit Card, Other Current Liability, Long Term Liability, Equity, Income, Cost of Goods Sold, Expense, Other Income, Other Expense, Deferred Revenue, Deferred Expense, Unbilled Receivable, Statistical.
  • Name.
  • Subsidiary. One or more. An account restricted to Subsidiary A cannot be used by a transaction posting to Subsidiary B.
  • Currency. A bank or credit card account is single-currency; other types can be multi-currency.

Optional but consequential:

  • Number. Only present once you enable account numbering at Setup > Accounting > Accounting Preferences > General > Use Account Numbers. It is a global toggle: on for every account, or off for every account.
  • Subtype. Additional narrowing for some types (an Other Current Asset can be flagged Prepaid Expense).
  • Parent account. For hierarchy. NetSuite lets you nest deeply and reads the leaves for reporting.
  • Include Children. Rolls the account up the subsidiary tree for consolidated reporting.
  • Restrict to Class / Department / Location. Blocks transactions from posting to the account unless they carry the specified segment. Useful once you have set up class and location segmentation.
  • General Rate Type. For revaluation of foreign-currency balances. Historical, Average, or Current.

None of these are exotic. They are the fields NetSuite expects every account to have. The migration decides whether to think about each one on purpose, or to accept whatever NetSuite defaults to when a CSV row is silent about them.

The source-to-destination template

This table is the starting mapping. Rows read left to right, source to destination. Anything called out in the fourth column is a place NetSuite will not error but will produce something you did not intend. Copy it into your own working sheet and fill in the specifics of your chart against these rows.

Source (QuickBooks) Source (Xero) NetSuite Type What to watch
Bank BANK Bank Currency must match. A multi-currency bank on the source side needs one NetSuite bank account per currency, or the reconciliation UI will not open the register.
Accounts Receivable (implicit) Accounts Receivable NetSuite defaults to one A/R per subsidiary per currency. Multiple source A/R accounts either consolidate on the way in or force every customer transaction to specify which A/R applies.
Other Current Assets CURRENT, PREPAYMENT Other Current Asset Xero’s PREPAYMENT usually becomes an Other Current Asset with a Prepaid subtype, not a top-level type of its own.
Fixed Assets FIXED Fixed Asset Accumulated depreciation is its own Fixed Asset account with a contra balance, not a subtype.
Other Assets NONCURRENT Other Asset
Inventory Asset (per item) INVENTORY Other Current Asset The Inventory Asset account is configured on the item record, not created as a type. The account itself is an Other Current Asset.
Accounts Payable (implicit) Accounts Payable Same one-per-subsidiary-per-currency default as A/R.
Credit Card (usually BANK) Credit Card Xero treats credit cards as bank accounts. Set the NetSuite type to Credit Card so the reconciliation and vendor bill UIs behave as they should.
Other Current Liabilities CURRLIAB, PAYGLIABILITY Other Current Liability If you use NetSuite’s SuiteTax module, tax liability accounts are created through the tax setup, not through the account import.
Long-term Liabilities TERMLIAB, LIABILITY Long Term Liability
Equity EQUITY Equity Retained Earnings is system-generated in NetSuite. Do not migrate the source Retained Earnings account; NetSuite will create its own and you will end up with two, with closing entries splitting between them.
Income REVENUE, SALES Income Xero’s SALES and REVENUE both collapse to Income. If your P&L relied on the Xero distinction, the split has to come back as a Class, Department, or Location, not a separate account type.
Cost of Goods Sold DIRECTCOSTS Cost of Goods Sold
Expense EXPENSE, OVERHEADS Expense Xero’s OVERHEADS is a P&L display distinction; NetSuite has no equivalent, so both map to Expense and the grouping moves into reporting.
Other Income OTHERINCOME Other Income
Other Expense DEPRECIATN Other Expense Depreciation in Xero is its own type; in NetSuite it is a normal expense account. The Fixed Asset Management module runs the depreciation schedules.
(none) (none) Deferred Revenue, Deferred Expense Not present as a type in either source. Add them during the CoA redesign, not from a mapping row.
(none) (none) Unbilled Receivable A NetSuite-only type used by Advanced Revenue Management for revenue recognised before the invoice is issued. Not created from a source-side mapping.
(none) (none) Statistical Non-financial quantitative accounts, for headcount or square footage. Add only if you use them.

Two things this table does not attempt. It does not map account numbers, because whether numbers survive depends on the next section. It does not map the parent-child hierarchy, because NetSuite reads hierarchy through a separate Parent Account field and QuickBooks encodes it as a colon-separated string inside the account name. An importer that reads that colon separator without splitting on it will happily create an account called Utilities:Electric at the top level of the tree, and the reports that group by parent will show it as its own root.

The duplicate-account problem

The template above assumes one source row maps to one NetSuite row. In practice, the source often has three or four rows that mean the same thing.

Office Supplies. Office Exp.. Office Expense. Office Supplies & Sundry. Someone renamed the account in 2021 and the historical journals kept posting to the old one. The company that got acquired last year had its own name for the same category. A well-meaning admin added a new one because they could not find the existing one. Six months in, nobody remembers which of the four is current, and the export from the source system has all of them.

Row 200 of that export is where you notice. Row 400 is where the pattern is undeniable. Somewhere behind the mapping spreadsheet, someone is ninety minutes into a VLOOKUP trying to work out whether Office Exp. and Office Expense are the same account or two.

They almost always are. What happens next depends on the mapping.

Import them all separately. NetSuite accepts each row as a distinct account, because none of them are exact string matches (a space here, an “e” missing there). You now have four Office Supplies accounts. The historical transactions that referenced Office Exp. post to one of them; the ones that referenced Office Expense post to another. Every report grouped by account splits the total across four lines instead of one.

Import them all under a canonical name. Every source variant maps to the same NetSuite account through the crosswalk. Transactions from all four historical variants post to the same account. Reports group correctly. Nothing about the source data changes; the crosswalk is what does the collapsing, on the way in.

Rely on NetSuite to dedupe you. NetSuite has no automatic account deduplication. Oracle’s Duplicate Record Detection supports Customer, Vendor, Partner, and Contact records; the chart of accounts is not one of them. Whatever the mapping imports as distinct stays distinct until someone merges by hand, and a manual merge of accounts is a more expensive operation than a manual merge of customers because every posted transaction has to move with it.

The choice is between doing the deduplication once, on the source side, before the import runs, or doing it every month afterwards as reports come out wrong.

The test that finds the duplicates you have is three passes over the source names. Sort them alphabetically and scan for near-matches. Then trim whitespace and lowercase them, and count the exact duplicates that produces. Then group by keyword (office, utility, travel, insurance) and scan for the semantic duplicates NetSuite will accept as distinct because the strings do not match. The third pass is the one that finds the forty-seven variations, and it is the one nobody runs when the pressure is on.

If the migration is also collapsing variants into a canonical name, keep the old names in the crosswalk so historical journals still reconcile. The Office Exp. account may not exist in NetSuite anymore, but the November 2023 rent journal still says it did.

The four decisions that shape the mapping

The template above gets you the type column right. Every other column depends on decisions you have to make explicitly. Answer them once for the whole chart, apply them consistently, and the import runs.

1. Keep source numbers, renumber, or turn numbering off?

QuickBooks and Xero both allow account numbers and both make them optional. NetSuite treats numbering as a global toggle. There is no per-account switch.

Three sensible defaults:

  • Keep the source numbers. The right call when the finance team already reads month-end reports by number, or when audit workpapers reference specific numbers. Requires numbering to be enabled in NetSuite before the first account imports.
  • Renumber during the migration. The right call when the go-live is also a chart-of-accounts redesign, or when the source numbering has grown organically and no longer follows a pattern. Publish the new-to-old crosswalk to the team a week before cutover.
  • Turn numbering off. The right call for a small, name-driven chart of accounts that never had numbers in the source. Rare for a business large enough to be moving to NetSuite.

What is not sensible is enabling NetSuite’s automatic numbering with source numbers already in place. NetSuite will assign its own sequence, ignoring whatever the source had, and the accounts show up under numbers nobody in the business recognises.

2. Flatten the hierarchy or preserve it?

QuickBooks nests accounts by putting : in the name. Xero does not nest, but many businesses fake hierarchy by prefixing account names (4000 Revenue - Product, 4100 Revenue - Consulting). NetSuite has a real Parent Account field and reads the tree from it.

There is a defensible case for either shape. A flat chart is easier to search and easier to reason about; a nested chart matches how finance thinks about the P&L. What is not defensible is a hybrid, where some accounts declare a parent and others encode it in the name. Every report that groups by parent will show the encoded ones as their own top-level nodes.

Pick the shape before the import. If you preserve the hierarchy, split the QuickBooks Parent:Child name into two columns before mapping, and populate Parent Account rather than embedding the parent in the child’s name.

3. Consolidate accounts or keep the split?

Every chart of accounts has splits that made sense at the time and no longer do. Meals & Entertainment - Client, Meals & Entertainment - Team, Meals & Entertainment - Recruiting. The migration is the cheapest moment to collapse them.

The test is not whether the split is neat. The test is whether anyone has run a report grouped by the split in the last twelve months. If the answer is no one, consolidate to one account and record the old three as inactive in the crosswalk so historical journals still reconcile.

The migration is also the most expensive moment to introduce splits that were not there before. Splitting one account into two after posting has begun means every historical journal is against the old account, and the split does not apply retroactively. Do the splits in a period of clean historical data, then migrate.

4. Which subsidiary owns each account? (OneWorld only)

If the destination is a NetSuite OneWorld account, every account must be restricted to at least one subsidiary. Restrict it to the wrong one, or forget to restrict it, and the transactions that reference it get rejected on import.

The default that works for most single-entity migrations is to restrict every account to the operating subsidiary and check Include Children. That means the account is available to that subsidiary and to any subsidiary underneath it in the tree.

Where the source system already runs multi-entity, the mapping has to decide whether the same account (4000 Revenue) exists once at the parent subsidiary and rolls up, or once per operating subsidiary and consolidates in reporting. Both are legitimate. What is not legitimate is doing one for some accounts and the other for others, because the consolidated P&L then reads inconsistently across sections.

What breaks when the decisions are skipped

Two failure modes worth naming, because both are inexpensive to prevent in the mapping and expensive to unwind afterwards.

The first is the subsidiary restriction miss. An account is created against Subsidiary A because that was the default when the CSV ran. The NetSuite UI would have caught this at data-entry time: a journal posting to Subsidiary B filters the Account dropdown down to accounts available to that subsidiary, and the Subsidiary-A-only account never appears in the list. The CSV and API paths have no such filter. So a batch of imported transactions posts to Subsidiary B, references the account, and NetSuite rejects the row. Running exactly that mismatch through a OneWorld sandbox returned:

Invalid account reference key 1500 Prepaid Expenses for subsidiary 40.

Two things worth reading in that message. The subsidiary is the internal ID (40), not the display name, so whoever is debugging has to look up which subsidiary that is. And the phrasing is the generic one NetSuite uses for any bad account reference: a typo in the number, a deleted account, an account with the wrong currency. Nothing in the text tells you the account exists but is restricted to a different subsidiary. You have to know to check.

Every transaction in the batch that references any Subsidiary A account fails on the same message. The fix is to widen the account’s subsidiary list and re-run, which is fine when it happens once. It is less fine when it happens across forty accounts and the finance team is trying to close the month.

The second is the type-change lockout. Once an account has a posted transaction against it, NetSuite will not let you change its type. So if the mapping puts a customer deposit into Other Current Asset when it belonged in Other Current Liability, and 200 journals post before anyone notices, the fix has three parts. A new account of the correct type. A journal moving the balance from the wrong account to the right one. And every downstream saved search, KPI, and financial statement that references the old account by internal ID updated to reference the new one.

The type is the field to get right first, because it is the field that cannot be changed cheaply once transactions start moving.

What to check before you map

  1. Confirm whether NetSuite account numbering is on, at Setup > Accounting > Accounting Preferences > General. It is a global toggle. Turn it on before the first account imports if you plan to keep source numbers, off if you do not.
  2. Publish the numbering decision to the team, whichever way it goes, so nobody looks up a report by an old number and reports a bug that is not one.
  3. Split hierarchical account names in the source before mapping. If QuickBooks stores Utilities:Electric, do not import the name with the colon in it. Split into a parent (Utilities) and a child (Electric) and populate Parent Account explicitly.
  4. Decide the subsidiary restriction rule up front for OneWorld, and apply it uniformly. “Every account restricted to the operating subsidiary with Include Children on” is a fine starting default.
  5. Delete the source Retained Earnings account from the import file. NetSuite generates its own. Migrating the source account gives you two, and closing entries land against whichever one the CSV imported first.
  6. Assign every source account an External ID before exporting. Reference accounts by External ID on every transaction import, not by name or number. The reasoning is the same as for customer name references, and it prevents the same class of downstream failure.
  7. Run the three-pass duplicate check on the source names: sort-alphabetically, trim-and-lowercase, and group-by-keyword. Add a canonical-name column to the mapping so every historical variant lands in one NetSuite account rather than four.
  8. Consolidate accounts that no report has read in twelve months, and record the collapse in the crosswalk so historical reconciliation still works.

The other fields on the account record follow the same reasoning that applies to every mapping decision in the migration.

How SuiteMigration handles it

SuiteMigration reads the QuickBooks or Xero chart of accounts directly and creates NetSuite accounts through the API, so the fields NetSuite requires on OneWorld and multi-currency accounts are set at creation rather than left to whatever the CSV wizard defaults to.

Every source account gets an External ID derived from the source system, and every posted transaction references accounts through that External ID. The Customer ID story from the customer name mapping applies here too: once the reference is on External ID, the account can be renumbered or renamed later without breaking any transaction that already posted against it.

Subsidiary restriction and currency are set per account on the way in, from rules the finance team configures once before the run. Retained Earnings is excluded automatically. Type is validated against Oracle’s enum before the API call goes out, so the type-change lockout does not happen after the fact.

Where a value has no equivalent on the source side, it is set in code from the run configuration rather than typed into an import wizard by whoever is running the load that day, which means the same account looks the same on every re-run of the migration.

Frequently asked questions

How do I map a QuickBooks or Xero chart of accounts to NetSuite?

Match each source account to a NetSuite account type from Oracle's fixed enum, then decide the numbering, hierarchy, subsidiary, and currency for each row before the import runs. The type list itself is short and mostly maps one-for-one. Everything that is not type is a field NetSuite requires at account create time and will silently default if the CSV row leaves it blank, which is why most account-mapping problems are decisions that were never made, not lookups that went wrong.

Should I keep my source account numbers or renumber during the migration?

Keep them if the finance team already reads month-end reports by number, or if audit workpapers reference specific numbers. Renumber if the go-live is also a chart-of-accounts redesign, or if the source numbering has grown organically and no longer follows a pattern. What you should not do is enable NetSuite's automatic account numbering with source numbers already in place, because NetSuite will assign its own sequence and the accounts show up under numbers nobody in the business recognises.

Can I have multiple A/R or A/P accounts in NetSuite like QuickBooks?

Yes, but NetSuite defaults to one A/R and one A/P per subsidiary per currency, and the customer and vendor payment screens assume that shape. Adding a second A/R account means every customer transaction now has to specify which one applies, and the shortcut summaries on customer and vendor records will not aggregate across them. Consolidate on the way in unless the split reflects a real reporting need.

Should I migrate my Retained Earnings account from QuickBooks or Xero?

No. NetSuite generates its own system-managed Retained Earnings account, so migrating the source one gives you two, and closing entries land against whichever one the CSV imported first. Delete the source Retained Earnings row from the account import file before running it. If the source Retained Earnings account carries a balance at cutover, migrate that balance as a journal to the NetSuite-generated account after the initial load, not as a second account of its own.

What error means my account mapping is wrong?

The message you will see on a transaction CSV import is Invalid account reference key <account> for subsidiary <internal ID>. The confusing part is that the same message means at least two different things: the account really does not exist under that reference in the destination, or the account exists but is restricted to a different subsidiary from the one the transaction is posting to. The text does not distinguish them, and the subsidiary comes back as an internal ID rather than a display name. Check the account's subsidiary restriction first; a typo is easier to spot once that is ruled out.

How do I stop the same account showing up twice in NetSuite?

Add a canonical-name column to the mapping and populate it before the import runs, so that every source variant of Office Supplies, Office Exp., and Office Expense points to the same NetSuite account. NetSuite has no automatic account deduplication; the Duplicate Detection & Merge feature supports Customer, Vendor, Partner, and Contact records, and the chart of accounts is not one of them. Whatever the mapping imports as separate accounts stays separate until someone merges by hand, which usually happens after the reports have already gone out wrong.

Get started

Planning a QuickBooks or Xero to NetSuite migration?

See how SuiteMigration moves your data, workflows, and history to NetSuite — validated and reconciled.