The short answer: the Data Import Wizard does not support Opportunities. Use Data
Loader or the API instead. Your file needs Name, StageName and
CloseDate — the only three fields Salesforce marks Required on create — plus
AccountId to attach the deal to an account. Write CloseDate as
yyyy-MM-dd, and make every StageName an active stage in your org.
Why the Data Import Wizard is out
The wizard imports accounts, contacts, leads, solutions, campaign members, person accounts and custom objects. Opportunity is not on that list, and no setting adds it. It also takes CSV only and caps at 50,000 records per run — limits worth knowing even when your object is supported, and covered in Data Import Wizard limits and workarounds.
So your options are Data Loader (free, from Salesforce, works against the API and can therefore load any object the API exposes), the Bulk API directly, or a third-party tool. Wider comparison in Salesforce import options.
What Salesforce actually requires
Straight from the Opportunity object reference. Three fields are marked Required. Everything else is optional at the API level — which does not mean optional for your business, nor that a validation rule in your org will not demand it anyway.
| Field (API name) | Type | Required on create? | What trips people up |
|---|---|---|---|
Name |
Text | Yes | Limit is 120 characters. Longer values are rejected, not truncated. |
StageName |
Picklist | Yes | Must be a value the field accepts — active, and available to the record type you are loading into. |
CloseDate |
Date | Yes | Bulk API wants yyyy-MM-dd. Excel will happily hand you 3/7/2026 instead. |
AccountId |
Lookup | No — the API allows it to be blank | Nearly always wrong to leave blank. An orphan Opportunity rolls up to nothing. |
Amount |
Currency | No | Once the Opportunity has products, Salesforce derives Amount from them and ignores updates to it. |
Probability, ForecastCategoryName |
Percent, picklist | No | Defaulted on create from StageName. Leave them out unless you are deliberately overriding. |
OwnerId |
Lookup | No | Defaults to the user running the load. If you want the real owners, map them. |
CurrencyIsoCode |
Restricted picklist | Multi-currency orgs only | Must be a currency your org allows — see below. |
Note the asymmetry: AccountId is not required by the API, but a missing or wrong one is
the failure people actually hit. Salesforce will happily create a headless Opportunity attached to no
account.
Linking each Opportunity to its Account
1. The Account's record Id
Put the 18-character Id in an AccountId column. Correct, but you first have to export
every Account with its Id and join that back to your source file. Export the 18-character form, not the
15-character one: 15-character Ids are case-sensitive, while Excel's lookup functions are not, so a
VLOOKUP can quietly treat 0018d00000AbCdE and 0018d00000abcde as the same key
and attach the wrong account to the deal. The 18-character Id carries a checksum suffix precisely so
that case-insensitive systems like Excel can handle it safely.
Two different errors come back when the Id is wrong, and they mean different things. A well-formed Id
that Salesforce cannot resolve — deleted record, wrong org or sandbox, an Id belonging to a different
object — fails as INVALID_CROSS_REFERENCE_KEY. A value that is not a Salesforce Id at all
— wrong length, stray characters, a truncated cell — fails earlier as MALFORMED_ID.
2. An External Id on Account (better)
If Account has a custom field with External Id ticked — your CRM's old account
number, an ERP key, a domain — Bulk API lets you reference the parent by that key instead of its
Salesforce Id, using a RelationshipName.IndexedFieldName column header:
Name,StageName,CloseDate,Account.Legacy_Account_No__c
Northwind renewal,Proposal/Price Quote,2026-09-30,ACC-10041
The relationship name on Opportunity's account lookup is Account, so the header is
Account.YourField__c. Two constraints from the Bulk API docs, both worth reading twice:
- Only indexed fields on the parent qualify. A custom field is indexed if its
External Id box is checked; a standard field only if its
idLookupproperty is true. - You cannot match Accounts by name.
Account.Namehas noidLookup, so that header will not work. If your file only has account names, you must resolve them to Ids or an External Id yourself, before the load. There is no way around it.
Child-to-parent only, and one level — you cannot reach a grandparent through this syntax.
Getting StageName and CloseDate past the parser
StageName is a picklist, and a picklist compares strings exactly. Your source system says
Proposal; the org says Proposal/Price Quote. A trailing space makes a
different string. A stage that is inactive, or not enabled for your record type, still shows
in Setup and is still rejected. That family of failures is pulled apart in
bad value for restricted picklist field.
CloseDate is a date field, and the Bulk API format for date fields is
yyyy-MM-dd. Anything ambiguous — 03/07/2026 is March 7 in the US and 3 July
in the UK — is a coin flip at best and a
type-conversion error at worst. Dates
landing one day early are a separate timezone problem:
imported dates are off by one day.
A file that works
Minimum viable insert, using the Account Id directly. Headers must match the field's API name exactly, and the file must be UTF-8:
Name,StageName,CloseDate,AccountId,Amount,Type
Northwind renewal,Proposal/Price Quote,2026-09-30,0018d00000ABCdeAAG,45000,Existing Customer - Upgrade
Contoso pilot,Qualification,2026-08-14,0018d00000XYZfgAAG,12000,New Customer
No Id column — you are inserting, not updating. No Probability — the stage
sets it.
The next wall: products and line items
Line items are a second, separate load into OpportunityLineItem — you cannot do it in the
same pass. Each row needs:
OpportunityId— required, so load the Opportunities first and get their Ids back.PricebookEntryId— required. NotProduct2Id: that has been read-only since API version 30.0, and the docs point you toPricebookEntryIdinstead. A PricebookEntry is a product in a specific price book, so it is a lookup per product per book.Quantity, plus eitherUnitPriceorTotalPrice.UnitPriceis required whenTotalPriceis not specified — but you cannot send both on the same row.
The price book behind that entry must also be the price book on the Opportunity
(Pricebook2Id); mismatch them and rows fail with a field-integrity error. If you only need
a deal value and not a product breakdown, set Amount and skip all of this — but remember
that once an Opportunity has products, Salesforce computes Amount from them and ignores whatever you
write there.
Multi-currency orgs
With multi-currency enabled, every Opportunity carries CurrencyIsoCode — a restricted
picklist of the ISO codes your org allows. An unlisted code is rejected outright, and if the
Opportunity has a price book set, its currency must match the currency of the PricebookEntry records
behind its line items.
Where these imports actually fail
| What you see | What it really is | Fix |
|---|---|---|
REQUIRED_FIELD_MISSING |
A blank Name, StageName or CloseDate — or a column you never mapped |
Required field missing |
| Bad value for restricted picklist | A stage name your org does not have, or one that is inactive or off the record type | Restricted picklist |
INVALID_CROSS_REFERENCE_KEY or MALFORMED_ID |
AccountId resolves to nothing (wrong org, deleted record) — or is not a well-formed Id at all |
Re-export the 18-character Ids, or switch to an Account External Id header |
| Cannot convert value to correct data type | CloseDate in a local format, or Amount carrying a currency symbol or thousands separator |
Type conversion |
| Field integrity exception on line items | PricebookEntry is in a different price book, or a different currency, than the Opportunity | Align Pricebook2Id and currency first |
The full index is at Salesforce import errors.
Doing the preparation before the load, not after it
Every hard part of this job happens before a record is written: resolving account keys, translating stage names, normalizing dates. Data Loader does none of it — it faithfully sends what you give it and hands back a CSV of errors.
That gap is what INQUA fills. It reads
your .xlsx or CSV as-is and joins your Opportunity file against an Account export —
up to four lookup files, with a match-rate preview, so you see how many rows found an account
before you commit. It captures the accepted StageName values from your org's describe
and flags every cell a restricted picklist would reject, and its Map values editor
lists the distinct stages in your file against a dropdown of the real ones. Dates are parsed with
an exact format you specify, so 03/07/2026 stops being ambiguous. Then either insert
via Bulk API 2.0 — insert or External-Id upsert only, double-confirmed, never a delete and never an
update by record Id — or export a typed .xlsx workbook onto
your own load template and run that through Data
Loader. It refuses the Salesforce upload while any cell error remains.
Be clear about what that does not buy you. It catches structural problems — wrong types, unknown picklist values, unresolved lookups, missing required columns. It cannot simulate your org: validation rules, Flows, triggers and duplicate rules all still run server-side, and any of them can reject a row that previewed perfectly. Nothing running outside your org can promise otherwise.
It is free during early access, and works with no Salesforce connection at all if you only want a clean, typed file to hand to Data Loader — see how the two compare.