Odoo imports vendor bills in bulk from an .xlsx or .csv file through its built-in import tool, mapping each row to the Vendor Bills (account.move) model. Relational fields such as vendors, products, and taxes must reference existing Odoo records by their External ID rather than by free text, and each bill can be brought in either as a draft for review or validated automatically so it posts on import. Those three facts cover the mechanics of how to import vendor bills into Odoo. They are also the part most guides stop at.
The harder problem sits in front of the importer. Odoo's import tool does what it promises once you hand it a clean, correctly structured file. Producing that file is the real work, because the source is almost never a spreadsheet. It is a folder of PDF and scanned supplier invoices in a dozen different layouts, and every one of them has to become a consistent row before Odoo will accept it.
This guide walks the whole path, not just the half the importer handles. First, getting structured rows out of your PDF bills. Then shaping those rows to match what Odoo's vendor bill import expects, including the External IDs that trip up most first attempts. Then running the import itself and the choices it forces, draft versus posted, numbering, currency, and tax. And finally, the decision underneath all of it: whether the native importer, a paid import module, or extracting first is the right path for your situation.
When Bulk Import Beats Entering Bills One at a Time
Three situations push teams toward spreadsheet-based bulk import, and they share a common shape: a large volume of bills that needs to land in Odoo in one controlled pass rather than trickling in document by document.
The first is a go-live or platform switch. When you migrate vendor bills to Odoo from another accounting system, the open and historical payables have to come across together so the AP ledger is complete from day one. Outstanding bills need to be payable, paid history needs to be queryable, and partial migrations leave gaps that surface during the first reconciliation.
The second is catch-up. A finance team that has fallen behind, or one that inherited a backlog when it adopted Odoo, may be sitting on hundreds or thousands of PDF and scanned bills. Importing historical supplier invoices into Odoo in bulk clears that backlog in a structured way instead of through weeks of manual keying.
The third is sustained volume. High-volume AP operations process more bills per day than manual entry can absorb, and a repeatable import is the only way to keep the ledger current without growing the data-entry team.
It is worth being precise about where this differs from Odoo's built-in OCR, because the two get conflated. Odoo's native vendor bill OCR for one-at-a-time digitization reads a single uploaded document, extracts its fields, and matches it against an existing purchase order. That is well suited to ongoing, day-to-day intake where bills arrive a few at a time and each one benefits from PO matching. It is the wrong tool for loading a thousand historical bills at once, where you do not want a document-by-document review loop and the purchase orders may not even exist in the new system. Bulk import is the migration-and-volume side of that line: you have already extracted the data, or are about to, and you want one reliable, repeatable load rather than a per-document workflow.
Turning a Pile of PDF Bills into Import-Ready Rows
An import is only ever as good as the file behind it. Odoo will faithfully load whatever you give it, which means a file with inconsistent dates, missing vendor references, or amounts that do not reconcile produces a ledger with exactly those problems, multiplied across every row. This is why the file-preparation step decides whether a migration succeeds, not the import button. Experian Data Quality research on the challenges of data migration projects found that while 91% of businesses undertake data migration projects, 85% encounter challenges with them, and a poorly structured source file is one of the most common reasons an otherwise routine import goes wrong.
So it helps to be concrete about what "import-ready" means for vendor bills. You need one consistent row structure across every bill: typically one row per bill when you only need header-level totals, or one row per line when you need the line-item detail and you repeat the invoice number down each line that belongs to the same bill. Dates need a single format. Numbers need a consistent decimal treatment so Odoo reads them as values, not text. Every row needs a stable invoice-number column, a vendor identifier, the tax treatment, and amounts that add up. Get those columns clean and consistent and the rest of the import is mechanical.
The obstacle is that your source rarely looks like that. Real AP arrives as PDFs and scans, each supplier using its own layout, its own labels, its own way of presenting tax and totals. Converting PDF bills to an Odoo import file means reading the data out of every one of those formats and normalizing it into a single layout, regardless of how the original document was arranged. A scanned bill from one supplier and a native PDF from another have to end up as identical row structures.
That normalization is exactly what our tool exists to do: turn a stack of PDF supplier invoices into a structured, import-ready spreadsheet. You upload the bills, describe the columns you need in plain language, and download a structured Excel or CSV file with one consistent row per bill or per line item. You can specify exactly the fields Odoo's template will expect, including invoice number, vendor name, dates, net, tax, and total, and tell it how to format each one, so varied supplier layouts collapse into a single clean structure. Every output row carries a reference back to the source file and page, so when a figure looks off during review you can check it against the original document rather than reopening a folder of PDFs by hand. This is the front half that neither Odoo's documentation nor the Apps Store module listings solve, because both assume you already have the clean file in hand.
The same intake problem shows up on the money-out side, and the approach is identical: if you are also importing bank statements into Odoo from PDF, you are doing the same extract-and-normalize work before the data is ready to load.
Mapping Your Spreadsheet to Odoo's Vendor Bill Fields
The most reliable way to build a correct import file is to let Odoo show you the shape it wants. Create one vendor bill manually, fill in the fields you care about, then export it. The exported file becomes your Odoo vendor bill import template: it carries the exact column names and field structure Odoo will recognize on the way back in, so you can drop your extracted data into those columns instead of guessing at field names. Building the template this way also confirms which fields are required versus optional for your configuration before you commit a full file.
Vendor bills live on the account.move model, the same model Odoo uses for all journal entries, with the document type in_invoice marking them as supplier bills rather than customer invoices or other entries. An import of vendor bills into Odoo from CSV or .xlsx targets that model, and the export-an-existing-bill step handles the model and type for you because the record you exported is already an account.move / in_invoice document. Both .csv and .xlsx work through the same native import tool.
External IDs are where most first attempts stall. Odoo stores relationships, not free text, so a vendor bill does not contain the word "Acme Supplies" in its partner field; it contains a reference to Acme's record. Relational fields (the vendor, the products on each line, the taxes, and the accounts) must therefore be supplied as External IDs that point at records already present in Odoo, not as plain names the importer is left to interpret. The practical consequence for a migration is twofold. You either prepare those records in Odoo first and reference them, or let the import create them, and you can reuse the External IDs from your previous system so the references stay stable and traceable across the move. Carrying over the old identifiers also makes it far easier to reconcile the migrated data against the source system afterward.
Products give you more matching flexibility than vendors. Odoo can match a product line by Name, by Internal Reference, or by Barcode, so you pick whichever identifier your extracted data carries most reliably, and there is a create-on-import option for products that do not yet exist in the catalog. Related to this is account selection: the expense or asset account for a line can be inherited from the matched product's own configuration, or set explicitly from a column in your file when you want to override the default or your products are not configured with accounts. For a bill-only migration where you are not loading a full product catalog, driving the account from a column in the file is often simpler than relying on product configuration.
One caution on all of this: Odoo's exact menu labels and option wording shift between versions. Treat the field intent described here as the stable part, the model, the External ID requirement, the matching options, and confirm the specific labels against the version you are importing into.
Running the Import: Draft vs Posted, Numbering, Currency, and Tax
The single most consequential choice at import time is whether bills come in as drafts or get validated automatically. Odoo lets you import bills as drafts, leaving them unposted so you can review and correct them in bulk before committing, or validate them automatically so they post the moment they import. The trade-off is straightforward. Automatic validation is faster and leaves you with posted, ready-to-pay bills in one step, but a posted bill is far harder to unwind than a draft, so an error in the file becomes an error in your ledger that needs a correcting entry. For a migration, importing as draft first is usually the safer order: load everything, scan the drafts for problems, fix the source file or the records, and post once you trust the batch.
Invoice numbering deserves a deliberate decision rather than a default. The bill number can come from a column in your file or be left to Odoo's own sequence. For a migration you almost always want it from the file, because preserving the original supplier invoice reference is what lets you match a bill in Odoo back to the paper or PDF it came from, and what auditors expect to see. Letting Odoo assign fresh sequential numbers severs that link. Keep the supplier's reference in a dedicated column and map it to the bill number field.
Multiple currencies can come in through a single import as long as each row specifies its currency and those currencies are activated in your Odoo database. You do not need to split a mixed-currency backlog into separate files; you need a currency column and the relevant currencies configured, and Odoo handles the per-bill conversion against the rates it holds.
Tax is the other place amounts go wrong. Taxes on the imported lines have to map to tax records that already exist in Odoo, referenced by External ID like any other relational field, and the mapping has to be correct for the tax and total on each bill to reconcile. A bill that imports with the wrong tax record, or none, will show amounts that do not match the original document, so confirm your tax mapping against a handful of bills before running the full file.
Once bills are posted they enter Odoo's normal accounts-payable flow, including Odoo's vendor bill approval workflow once bills are posted if your organization runs one, so a clean import feeds directly into the same review and payment process as bills entered any other way.
Native Import, an Apps Store Module, or Extract-Then-Import?
Three paths get vendor bills into Odoo in bulk, and they solve different parts of the problem rather than competing head to head.
Odoo's native import tool is free, built in, and imports any business object, including vendor bills, from an .xlsx or .csv file. It is the right choice when you already have a clean, structured file and your need is one-off or occasional, such as a single migration you will not repeat. It does everything covered above: account.move targeting, External IDs, draft or validated posting. What it does not do is help you produce the file.
A paid Apps Store import module sits on top of that. These modules add convenience, such as guided field-mapping interfaces, validation feedback, and saved mapping profiles, which earn their cost for teams running imports regularly or anyone who wants a friendlier mapping step than the native tool offers. But a module is still an importer. It presupposes you have arrived with structured data, exactly as the native tool does.
Extract-then-import solves the part the other two assume away. When your source is a pile of PDF and scanned bills, the bottleneck is producing the structured file in the first place, and that is the step extraction adds ahead of either importer. You convert the bills into a clean Excel or CSV file, then run that file through the native tool or a module. This is where extraction fits: it produces the import-ready file the native importer and the Apps Store modules both require but neither creates.
That reframes the decision for most readers. The native importer versus a module is a question about convenience and how often you import. But if your bills start as PDFs, the more important question is how you produce the structured file at all, because without it neither importer has anything to work with. The same pattern holds across accounting platforms, not just Odoo. The mechanics differ when you import vendor bills into NetSuite or any other system, but the discipline of producing a clean, well-structured source file first is what carries over, so the work you put into file preparation pays off wherever your bills need to land.
Extract invoice data to Excel with natural language prompts
Upload your invoices, describe what you need in plain language, and download clean, structured spreadsheets. No templates, no complex configuration.
Related Articles
Explore adjacent guides and reference articles on this topic.
How to Import Bank Statements into Odoo (PDF & Formats)
Odoo won't import a PDF bank statement directly. Learn its supported formats (OFX, CAMT.053, CSV), the exact import path, and how to convert a PDF first.
Odoo Vendor Bill OCR: Workflow, PO Matching, Limits
How Odoo vendor bill OCR handles PO matching and Auto-complete. Where native digitization falls short and when upstream extraction cuts bill cleanup.
FreshBooks Purchase Orders and Vendor Bills
Learn where FreshBooks vendor bills support PO control, where PO matching stops, and how to check supplier invoices before payment.