Getting Started

Mapping CSV columns to custom postmeta

Published · Last updated

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 keyPurpose
_skuStock-keeping unit. Also doubles as SSI’s default unique identifier.
_regular_pricePre-sale price.
_sale_priceSale price (use alongside _regular_price).
_priceCurrent effective price (WC sets this from regular/sale; you can write it directly too).
_stockStock quantity. Requires _manage_stock = 'yes'.
_stock_statusinstock / outofstock / onbackorder.
_manage_stockyes / no.
_backordersno / notify / yes.
_weightProduct weight in the configured WC weight unit.
_length, _width, _heightProduct dimensions.
_tax_statustaxable / shipping / none.
_tax_classTax class name (empty = standard).
_purchase_noteFree-text note shown after purchase.
_sold_individuallyyes to limit to one per order.
_virtualyes for digital/service products.
_downloadableyes for downloadable products.
_product_urlExternal product URL (for external/affiliate products).
_button_text“Buy on Amazon”, “View product”, etc. (for external products).
_visibilityvisible / catalog / search / hidden (WC < 3.0; modern WC uses the product_visibility taxonomy instead).
_featuredyes / 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, and the item has no row for that meta key yet → insert-postmeta writes no row. Product imports are the exception for _stock and _tax_class, which are always written, as WooCommerce does.
  • Empty CSV cell, and the item already has that meta key → update-postmeta sets meta_value = '' (empty string). A blank price cell therefore clears the price.
  • 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 (a mapped meta key names a column that is not in the file’s header) → that key is left as it is: no row is inserted and an existing value is not changed. Only a missing unique identifier column, or a missing parent grouping column for variations, stops the import during load-csv.

If you want a postmeta cell to be DELETED rather than set to empty, you 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. Pick the After Import tasks the store needs (term recounts, WooCommerce lookup tables, search and filter index rebuilds). When a plugin (Yoast, a search indexer and so on) needs save_post to fire for every imported post, run wp ssi <id> save-posts after the import. See Run your own code when an import completes for hooking your own follow-up work.

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.

Written for Super Speedy Imports 3.40.0.

Comments

▲
▼
1/1