Mapping CSV columns to custom postmeta

June 5, 2026

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.

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. (Edit wp-config.php or use wp 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.
×
1/1