Mapping CSV columns to custom postmeta
WordPress stores almost every non-core property of a post as postmeta — rows in wp_postmeta keyed by post ID and a meta key. SKUs, prices, dimensions, custom ACF fields, custom plugin data — all postmeta.
This article covers how to map CSV columns to postmeta in SSI, both the well-known WooCommerce keys and your own custom ones.
Table of Contents
Where postmeta lives in the config
In an SSI import config, postmeta mappings live in the post_meta block of template_mappings. The format is "meta_key" => "CSV column name":
"template_mappings": {
"post_title": "Name",
"post_meta": {
"_sku": "SKU",
"_regular_price": "Regular_Price",
"_stock_status": "Stock_Status",
"_dimensions_volume": "Volume_Litres",
"vendor_internal_id": "Vendor_ID"
},
...
}
For every CSV row, SSI writes:
– wp_postmeta(post_id, meta_key='_sku', meta_value=<CSV's SKU column>)
– wp_postmeta(post_id, meta_key='_regular_price', meta_value=<CSV's Regular_Price column>)
– …and so on for each entry.
When a column maps to a meta_key starting with _, WordPress treats it as “hidden meta” (not shown in the admin’s Custom Fields metabox). Keys without _ show up there.
Built-in WooCommerce meta keys
The product-level keys WC reads from postmeta. SSI handles them like any other postmeta — just map your CSV columns:
| Meta key | Purpose |
|---|---|
_sku |
Stock-keeping unit. Also doubles as SSI’s default unique identifier. |
_regular_price |
Pre-sale price. |
_sale_price |
Sale price (use alongside _regular_price). |
_price |
Current effective price (WC sets this from regular/sale; you can write it directly too). |
_stock |
Stock quantity. Requires _manage_stock = 'yes'. |
_stock_status |
instock / outofstock / onbackorder. |
_manage_stock |
yes / no. |
_backorders |
no / notify / yes. |
_weight |
Product weight in the configured WC weight unit. |
_length, _width, _height |
Product dimensions. |
_tax_status |
taxable / shipping / none. |
_tax_class |
Tax class name (empty = standard). |
_purchase_note |
Free-text note shown after purchase. |
_sold_individually |
yes to limit to one per order. |
_virtual |
yes for digital/service products. |
_downloadable |
yes for downloadable products. |
_product_url |
External product URL (for external/affiliate products). |
_button_text |
“Buy on Amazon”, “View product”, etc. (for external products). |
_visibility |
visible / catalog / search / hidden (WC < 3.0; modern WC uses the product_visibility taxonomy instead). |
_featured |
yes / no (WC < 3.0; modern WC uses product_visibility taxonomy featured term). |
Mapping any of these is no different from a custom meta_key.
Custom meta keys
For your own postmeta (ACF fields, theme metadata, custom plugin keys), just use the meta_key name directly:
"post_meta": {
"isbn": "ISBN",
"publisher": "Publisher",
"publication_year": "Year",
"_show_in_homepage": "Featured_On_Homepage"
}
No prefix conventions to worry about — whatever your code reads from the postmeta is what you put on the left side.
Serialised values
Some plugins store complex data as PHP-serialised arrays in a single postmeta row (a:3:{i:0;s:5:"red";i:1;s:5:"blue";...}). SSI takes the CSV value VERBATIM and writes it to the postmeta cell. If your CSV column contains the serialised string already, this works as-is. If your CSV has structured data (multiple columns) and you need to serialise it to one meta value, you’ll need a PHP function mapping — see the developer “Creating custom templates” article.
Empty / NULL behaviour
- Empty CSV cell → SSI writes
meta_value = ''(empty string). - Whitespace-only CSV cell → SSI writes the whitespace as-is. (Most plugins treat this as missing; some don’t. Clean your CSV.)
- Missing CSV column entirely (mapped meta key references a column not in the CSV header) → the import errors during
load-csv(“could not find column X”). Fix the mapping or the CSV.
If you want a postmeta cell to be DELETED rather than set to empty, you currently can’t do that purely through column mapping — you’d need to add a custom stage. Most users just write null or 0 and have their template code interpret that.
Mapping the same CSV value to multiple meta keys
Common when a column should populate both a built-in WC key and your own custom key:
"post_meta": {
"_sku": "Vendor_SKU",
"vendor_internal_sku": "Vendor_SKU"
}
Both get written. Useful when you want WC to use the vendor’s SKU for matching but also want to display it in a custom theme widget that reads vendor_internal_sku.
When NOT to use postmeta
For data that:
- Has many possible values and you want to filter / count by it → make it a taxonomy instead. See “Mapping hierarchical and multi-path taxonomies”.
- Belongs to multiple posts (a brand object with its own description / logo) → consider a related custom post type instead. See the developer “Creating custom templates” article on cross-CPT lookups.
- Is a single global setting → use
wp_options, not postmeta. (Editwp-config.phpor usewp option update.)
Postmeta on variable products and variations
For variable products, the parent and each variation get their own postmeta. SSI handles this automatically when you set up the parent_sku mapping — see the dedicated Importing variable products article. The short version:
- Postmeta keys mapped on the variable parent’s CSV row are written to the parent post.
- Postmeta keys on each variation row are written to that variation post.
- Some keys (e.g.
_sku,_regular_price) are typically per-variation; others (e.g._visibility,_product_type) are parent-only.
Performance note
Postmeta inserts are batched. For a 100k-row import with 10 mapped postmeta keys, SSI runs 10 bulk INSERT statements (one per key), each writing 100k rows. This is roughly two orders of magnitude faster than calling update_post_meta() per (post, key) pair like a hand-rolled importer would.
The trade-off: any plugin that hooks added_post_meta / updated_post_meta to do extra work per-row DOESN’T see the bulk insert. SSI runs a save-posts pass at the end of the import that re-fires save_post per post so hook-listening plugins (Yoast, search indexers, etc.) get their chance. See the scheduling automated imports with cron article for the full re-trigger story.
What’s next
- Importing variable products — for parent/variation postmeta splits.
- Choosing your unique identifier — usually
_sku, sometimes a custom postmeta key. - Creating custom templates (developer KB) — for postmeta whose value needs computing from multiple CSV columns.