BAPI_PO_CREATE1: EXTENSIONIN, Example, and Custom Fields
Aug 19, 2026
Aug 19, 2026
Convert a purchase order to Excel, CSV, or JSON
Submit your purchase orders
Drop documents here, or click to file
Up to 50 files per batch
Uploading...
BAPI_PO_CREATE1 is the SAP function module that creates a purchase order programmatically, running the same logic as the ME21N transaction. Two things trip up almost everyone on the first call. POHEADER and POHEADERX are IMPORTING parameters, not tables, while items, schedule lines and account assignments are passed as TABLES. And every field you populate has to be flagged in a matching X structure, or the BAPI ignores it silently. Nothing is written to the database until you call BAPI_TRANSACTION_COMMIT afterwards.
Last updated August 2026
It creates a purchase order from data your program supplies, without anybody sitting in ME21N. Interfaces, migrations, supplier portals and scheduled jobs all use it. Because it drives the standard purchase order logic underneath, the same determinations fire that would fire on manual entry: pricing, account assignment defaults, release strategy, tolerance checks and availability. That is the point of using a BAPI rather than writing directly to EKKO and EKPO, which you should never do.
The BAPI replaced the older BAPI_PO_CREATE, and the "1" in the name is the version marker, not a sequence number. Its sibling for changes is BAPI_PO_CHANGE, which takes the same X structure discipline. The PORDCR1 purchase order IDoc feeds this same function module, so anything you learn debugging one applies to the other.
What it does not do is read a document. If your input is a supplier order confirmation as a PDF, the BAPI is the last step, not the first. Something has to turn that page into POHEADER and POITEM values before the call exists at all.
The interface is large, and most examples online show only the eight or nine parameters they happened to need. The full picture matters because the parameter category tells you how to pass the data.
| Category | Parameter | What it carries |
|---|---|---|
| IMPORTING | POHEADER | Header data, type BAPIMEPOHEADER |
| IMPORTING | POHEADERX | Header change flags, type BAPIMEPOHEADERX |
| IMPORTING | POADDRVENDOR | A one-off vendor address that overrides central address management |
| IMPORTING | TESTRUN | Simulate only, nothing is created |
| IMPORTING | MEMORY_COMPLETE / MEMORY_UNCOMPLETE | Hold the order if it is clean, or hold it if it is faulty |
| IMPORTING | PARK_COMPLETE / PARK_UNCOMPLETE | Park the document instead of posting it |
| IMPORTING | NO_PRICE_FROM_PO | Do not adopt the price from the last document |
| IMPORTING | NO_MESSAGING / NO_MESSAGE_REQ | Suppress output determination |
| IMPORTING | NO_AUTHORITY | Skip the authorization check |
| IMPORTING | VERSIONS | Version management data |
| EXPORTING | EXPPURCHASEORDER | The new PO number, type BAPIMEPOHEADER-PO_NUMBER |
| EXPORTING | EXPHEADER | The full header as created |
| TABLES | POITEM / POITEMX | Line items and their change flags |
| TABLES | POSCHEDULE / POSCHEDULEX | Schedule lines, which is where delivery dates live |
| TABLES | POACCOUNT / POACCOUNTX | Account assignment rows |
| TABLES | POCOND / POCONDX | Item conditions |
| TABLES | POCONDHEADER / POCONDHEADERX | Header conditions |
| TABLES | POSERVICES, POSRVACCESSVALUES, POSERVICESTEXT | Service specifications for service orders |
| TABLES | POLIMITS, POCONTRACTLIMITS | Value limits on limit items |
| TABLES | POADDRDELIVERY | A one-off delivery address per item |
| TABLES | POTEXTHEADER / POTEXTITEM | Header and item long texts |
| TABLES | POPARTNER | Partner roles other than the vendor itself |
| TABLES | POSHIPPING / POSHIPPINGX | Shipping data |
| TABLES | POCOMPONENTS / POCOMPONENTSX | Subcontracting components |
| TABLES | SERIALNUMBER / SERIALNUMBERX | Serial numbers |
| TABLES | INVPLANHEADER, INVPLANITEM (and their X tables) | Invoicing plan data |
| TABLES | EXTENSIONIN / EXTENSIONOUT | Customer enhancement fields |
| TABLES | RETURN | Messages, which you must read before committing |
Note the split. Because POHEADER is an IMPORTING parameter, you pass a single work area, not an internal table. Passing a table there is the most common syntax error in a first attempt.
SAP cannot tell the difference between "leave this field alone" and "set this field to blank" when everything arrives as one structure. So every data structure has a twin whose fields are single characters. You set the twin field to X for each field you actually filled. Anything you leave unflagged is treated as not supplied, which means the BAPI applies its own defaults or ignores your value entirely.
This is why a purchase order gets created with the right vendor and the wrong plant, or with a quantity of zero, while the RETURN table shows nothing worse than a success message. The value was there in POITEM; the flag was missing in POITEMX, so it was never read.
The item tables add one wrinkle. In POITEMX, the PO_ITEM field is the key and carries the actual item number, not an X, while a separate PO_ITEMX flag marks the row as new. Same pattern in POSCHEDULEX, where PO_ITEM and SCHED_LINE identify the row and the remaining fields carry flags.
Here is the shape of a stock material purchase order with one item. Field names are the real interface names, and the X structures mirror the fields that were filled.
DATA: ls_header TYPE bapimepoheader,
ls_headerx TYPE bapimepoheaderx,
lt_item TYPE TABLE OF bapimepoitem,
lt_itemx TYPE TABLE OF bapimepoitemx,
lt_sched TYPE TABLE OF bapimeposchedule,
lt_schedx TYPE TABLE OF bapimeposchedulx,
lt_return TYPE TABLE OF bapiret2,
lv_po_number TYPE bapimepoheader-po_number.
ls_header-comp_code = '1000'.
ls_header-doc_type = 'NB'.
ls_header-vendor = '0000100234'.
ls_header-purch_org = '1000'.
ls_header-pur_group = '001'.
ls_header-doc_date = sy-datum.
ls_headerx-comp_code = 'X'.
ls_headerx-doc_type = 'X'.
ls_headerx-vendor = 'X'.
ls_headerx-purch_org = 'X'.
ls_headerx-pur_group = 'X'.
ls_headerx-doc_date = 'X'.
APPEND VALUE #( po_item = '00010'
material = '000000000000004711'
plant = '1000'
quantity = '100'
po_unit = 'EA'
net_price = '12.50' ) TO lt_item.
APPEND VALUE #( po_item = '00010' " key, not a flag
po_itemx = 'X' " this row is new
material = 'X'
plant = 'X'
quantity = 'X'
po_unit = 'X'
net_price = 'X' ) TO lt_itemx.
APPEND VALUE #( po_item = '00010'
sched_line = '0001'
delivery_date = sy-datum + 14
quantity = '100' ) TO lt_sched.
APPEND VALUE #( po_item = '00010'
sched_line = '0001'
po_itemx = 'X'
sched_linex = 'X'
delivery_date = 'X'
quantity = 'X' ) TO lt_schedx.
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING poheader = ls_header
poheaderx = ls_headerx
no_price_from_po = abap_true
IMPORTING exppurchaseorder = lv_po_number
TABLES return = lt_return
poitem = lt_item
poitemx = lt_itemx
poschedule = lt_sched
poschedulex = lt_schedx.
IF line_exists( lt_return[ type = 'E' ] ) OR line_exists( lt_return[ type = 'A' ] ).
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.
ENDIF.
Two details in that snippet do real work. NO_PRICE_FROM_PO set to true stops SAP quietly overwriting your net price with the price from the last order for that vendor and material, which is the reason so many interfaces post the wrong value. And WAIT set to true on the commit makes the call synchronous, so a follow-on read of the new order actually finds it instead of racing the update task.
Most teams end up wrapping this in a small reusable class so mapping, error handling and the commit decision live in one place rather than being copied into every interface. That boilerplate is exactly the kind of work worth handing to an AI coding assistant that can plan and draft the whole wrapper before a developer reviews it against the interface above.
The delivery date is not a header field and it is not an item field. It lives on the schedule line, in POSCHEDULE-DELIVERY_DATE, with the matching flag in POSCHEDULEX. If you skip POSCHEDULE entirely, the BAPI usually still creates the order but dates it however the vendor and material defaults dictate, which is rarely what the source document said.
One item can carry several schedule lines, numbered in SCHED_LINE, and the quantities on those lines should add up to the item quantity. When they do not, SAP takes the schedule as the truth and the item quantity gets adjusted, which looks like corruption until you know the rule. Delivery date category and time can also be set here through DEL_DATCAT_EXT and DELIV_TIME.
Standard fields have named parameters. Custom fields appended to EKKO or EKPO do not, so SAP provides a generic escape hatch: the EXTENSIONIN table, typed BAPIPAREX. Each row of that table is a structure name plus a flat character payload.
| Field | Type | What goes in it |
|---|---|---|
| STRUCTURE | CHAR 30 | The name of the extension structure, for example BAPI_TE_MEPOHEADER |
| VALUEPART1 | CHAR 240 | The first 240 characters of the flattened structure |
| VALUEPART2 | CHAR 240 | Characters 241 to 480 |
| VALUEPART3 | CHAR 240 | Characters 481 to 720 |
| VALUEPART4 | CHAR 240 | Characters 721 to 960 |
So one EXTENSIONIN row carries at most 960 characters of data, split across four fields purely because of the field length limit. You fill the extension structure normally, then move it into VALUEPART1 and let the overflow spill into the later parts.
The extension structures follow the same twin pattern as everything else. For header fields you append BAPI_TE_MEPOHEADER for values and BAPI_TE_MEPOHEADERX for flags. For item fields it is BAPI_TE_MEPOITEM and BAPI_TE_MEPOITEMX. Account assignment fields go through BAPI_TE_MEPOACCOUNTING and its X twin.
DATA: ls_te_header TYPE bapi_te_mepoheader,
ls_te_headerx TYPE bapi_te_mepoheaderx,
lt_extin TYPE TABLE OF bapiparex,
ls_extin TYPE bapiparex.
ls_te_header-po_number = ''. " left blank on create
ls_te_header-zzcontract = 'C-4471'. " a custom EKKO field
ls_te_headerx-po_number = ''.
ls_te_headerx-zzcontract = 'X'.
ls_extin-structure = 'BAPI_TE_MEPOHEADER'.
ls_extin-valuepart1 = ls_te_header.
APPEND ls_extin TO lt_extin.
CLEAR ls_extin.
ls_extin-structure = 'BAPI_TE_MEPOHEADERX'.
ls_extin-valuepart1 = ls_te_headerx.
APPEND ls_extin TO lt_extin.
Both rows are required. Sending the value row without the X row is the same mistake as filling POITEM without POITEMX, and it fails the same silent way.
Message ME887, "Error transferring ExtensionIn data for enhancement CI_EKKODB", is the classic symptom. The usual cause is that one of your custom fields on EKKO or EKPO is not character typed. VALUEPART1 through VALUEPART4 are CHAR fields, so the whole extension payload is moved as characters. A packed or numeric field does not survive that move cleanly, and the transfer routine rejects the row.
There are two standard answers. The first is to keep the append fields on CI_EKKODB and CI_EKPODB character typed wherever you can, converting to numeric only after the BAPI has run. The second is to take control of the mapping yourself, which is what the BAdIs below exist for.
A separate and equally common cause is that the extension structures were never appended in the first place. BAPI_TE_MEPOHEADER does not automatically inherit the fields you added to EKKO. You append your custom fields to the extension structure and to its X twin explicitly, and until you do, the BAPI has nowhere to put the values.
Three come up, and they do different jobs.
| BAdI | When it runs | Typical use |
|---|---|---|
| ME_BAPI_PO_CUST | Inside the BAPI, around the customer enhancement handling | Map EXTENSIONIN values onto the document when the generic transfer will not do it |
| MEOUT_BAPI_CUST | On the mapping of extension data, method MAP2I_EXTENSIONIN | Convert non-character custom fields into the document structures by hand |
| ME_PROCESS_PO_CUST | On every purchase order change, whether from ME21N or a BAPI | Enforce validations and derivations centrally so the interface and the transaction behave the same |
There is also a no-code workaround that a lot of shops use: add a dummy character field to CI_EKKODB and CI_EKKODBX so the generic transfer routine has something it can move, which is enough to make the standard mechanism engage. It works, but it is worth writing down somewhere, because the next developer will not guess why that field exists.
If your requirement is really a validation rather than a field, put it in ME_PROCESS_PO_CUST. Logic that only lives in the BAPI wrapper gets bypassed the first time somebody keys an order manually.
TESTRUN set to X runs every check and fills RETURN without creating anything. Use it in the first pass of any migration, because it turns a 4,000 record load into a list of the 60 records that will actually fail. Nothing is committed, so no rollback is needed.
MEMORY_COMPLETE and MEMORY_UNCOMPLETE hold the order rather than posting it, which is useful when a buyer should review before release. PARK_COMPLETE and PARK_UNCOMPLETE do the equivalent for parked documents. All four are alternatives to a normal post, not additions to it.
NO_AUTHORITY deserves a warning. It skips the authorization check, so a job running under a technical user can create orders it would otherwise be refused. That is sometimes correct for a background interface, and it is a finding in an audit if nobody documented the decision.
RETURN is typed BAPIRET2 and comes back populated even on success. The TYPE field carries S for success, I for information, W for warning, E for error and A for abort. Only E and A mean the document was not created. Warnings are routine, and a wrapper that treats every non-S row as a failure will roll back perfectly good orders.
The rule that matters: check RETURN before you commit, and call BAPI_TRANSACTION_ROLLBACK when you find an E or an A. Skipping the rollback leaves the update task holding partial work that the next commit in the same LUW will happily write. EXPPURCHASEORDER also comes back empty when the create failed, which makes it a useful second check.
BAPI_PO_CREATE1 is the current one and BAPI_PO_CREATE is the obsolete predecessor. The newer version exposes far more of the purchase order: schedule lines, conditions, services, limits, partners, texts and the EXTENSIONIN mechanism for custom fields. New development should use BAPI_PO_CREATE1, and BAPI_PO_CHANGE is its counterpart for changes.
Yes. The BAPI prepares the document but does not commit it, so without the explicit commit the work is discarded at the end of the transaction. Call BAPI_TRANSACTION_COMMIT with WAIT set to true if your program reads the new order immediately afterwards, otherwise the update task may not have finished writing when you look.
Populate PREQ_NO and PREQ_ITEM on the POITEM row and flag both in POITEMX. SAP then pulls the requisition data forward and links the documents, so the requisition shows as consumed. You still supply quantity and delivery date yourself if they differ from the requisition.
Yes, but it needs more tables. The item carries item category D, and the service specifications go into POSERVICES with their account distribution in POSRVACCESSVALUES. Service orders are the most fiddly case in the whole interface, largely because the outline levels in POSERVICES have to be numbered consistently with each other.
Because by default SAP adopts the price from the last document for that vendor and material. Set NO_PRICE_FROM_PO to X to stop it. If you need full control of the pricing result rather than just the net price, pass the conditions explicitly through POCOND and POCONDX, which maps onto the purchase order pricing condition tables behind the scenes.
In the standard purchasing tables. The header is written to EKKO, the items to EKPO, the schedule lines to EKET and the account assignments to EKKN. Reading those tables after a load is the quickest way to confirm what the BAPI actually stored, as opposed to what you thought you sent. See the EKKO and EKPO reference for the field level detail.
For the tables the BAPI writes into, start with the SAP purchase order tables EKKO and EKPO and the purchase order history tables that record what happened after the order was created. Orders created this way still run through approval, which is documented in the release strategy tables. If your load arrives as an IDoc rather than a direct call, the purchase order IDoc reference covers PORDCR1.
If the data you are trying to load starts life as a supplier PDF, the SAP purchase order import page shows how to get from document to structured fields before the BAPI call, and smaller SAP shops running the SMB product should read the SAP Business One purchase order import guide instead, since B1 uses the Data Transfer Workbench and the OPOR and POR1 tables rather than BAPIs.
Stop retyping purchase orders
Upload a PDF, scan, or photo of any PO and get clean Excel, CSV, or JSON line items in seconds.
Try it free25 pages free. No credit card required.