Skip to content

WooCommerce Import

Import from WooCommerce via a direct read of the WordPress/WooCommerce database or via the Woo REST API. The direct-database mode is the strongest path for a heavily customized shop whose data only exists in its tables; both modes normalize to the same field vocabulary, so one reviewed mapping serves either, and meta.* / usermeta.* fields are auto-routed into Vectis custom fields.

At a glance

Extension id woocommerce
Category migration
Version 0.1.0
Provides Import provider (products, variants, collections, customers, orders)
External account WooCommerce REST API consumer key/secret, or read access to the store's database (per source mode)

What It Provides

  • An import provider with key woocommerce (label "WooCommerce"), registered on the strategy resolver when the extension activates. It appears in the provider list of the admin Import Wizard via the importProviders GraphQL query.
  • Two source modes: source_db (direct database read) and admin_api (Woo REST API at /wp-json/wc/v3/).
  • Five entity types: product, variant, collection, customer, order, with a dependency DAG — variants require products; orders require customers, products, and variants. Runs import dependencies before dependents, and the wizard flags unselected prerequisites.
  • Default field mappings per entity over the shared logical vocabulary (name, slug, description, status, sku, price, external_id, …). WooCommerce post_status values map publishpublished, draft/pendingdraft, private/trasharchived. Discovered meta.* and usermeta.* fields are automatically routed into custom_fields.
  • Import runs execute through the core ImportRunWorkflow Temporal workflow (Building Extensions covers the strategy surface). The extension itself registers no workflows, webhook endpoints, admin pages, or GraphQL fields of its own.

Setup

  1. The extension loads at boot; it should appear under Settings > Extensions.
  2. Open Settings > Import Wizard in the admin (/settings/import).
  3. Provider step: pick WooCommerce.
  4. Entities step: choose what to import. The wizard flags unselected prerequisites (e.g. picking order without product/variant/customer) and offers to add them, so cross-entity references stay linked.
  5. Connect step — enter the connection config as JSON for the chosen source mode:
    • Direct source databasedsn (e.g. mysql+aiomysql://user:pw@host:3306/woo) and optionally table_prefix (default wp_).
    • Live Admin APIstore_url, consumer_key, consumer_secret (the standard Woo REST key pair, sent as HTTP Basic auth).
  6. Map & review step: the provider's default mapping is pre-filled per entity; unmapped source fields can be routed into custom_fields, and each entity's mapping can be saved as a reusable import profile.
  7. Dry run step: previews create/update/skip/fail counts without writing anything — every row runs in a savepoint that a dry run rolls back.
  8. Run step: executes the import.

There is no separate test-connection step in the wizard: bad credentials surface when the wizard first discovers source fields on the way into Map & review. (The connectors implement a test_connection hook — DB mode runs SELECT 1 against the posts table, REST mode fetches one product from /products — but no wizard step currently invokes it.)

Configuration Reference

This provider has no extension-level config_schema — connection details are entered per run in the wizard's Connect step and are not stored (the persisted import-run record keeps the plan, not the connection config). Per source mode:

Direct source database mode

Key Type Required Secret Description
dsn string Yes Yes SQLAlchemy async DSN, e.g. mysql+aiomysql://user:pw@host:3306/woo
table_prefix string No No WordPress table prefix, defaults to wp_

Admin API mode

Key Type Required Secret Description
store_url string Yes No e.g. https://shop.example.com
consumer_key string Yes Yes WooCommerce REST API consumer key
consumer_secret string Yes Yes WooCommerce REST API consumer secret

The DB connector also honors an optional batch_size key (default 500) controlling how many rows are fetched per keyset batch.

Permissions

The manifest declares no permissions of its own. Running imports and saving mapping profiles require the core import_export.import permission.

Operational Notes

  • DB mode tables — products from posts (post_type='product') + postmeta + wc_product_meta_lookup; variations from posts (post_type='product_variation'); collections from terms/term_taxonomy (product_cat taxonomy); customers from users + usermeta; order headers from the HPOS wc_orders and wc_order_addresses tables; order lines from woocommerce_order_items + woocommerce_order_itemmeta. All table names use the configured table_prefix, which is validated against ^[A-Za-z0-9_]+$ (an unsafe prefix is rejected outright).
  • DB mode performance — pagination is keyset-by-id in batches, and postmeta/usermeta/item meta are fetched per batch rather than per row to avoid N+1 queries.
  • Simple products get a default variant — any product with a SKU in wc_product_meta_lookup yields one variant, so simple (non-variable) products import with a sellable variant.
  • Order-line SKU resolution (DB mode) — each line's _variation_id (variable products) or _product_id (simple products) is resolved to a real SKU through wc_product_meta_lookup, so the order loader can match lines by SKU uniformly.
  • REST mode pagination — 100 records per page via the X-WP-TotalPages header, with a 60-second HTTP timeout; variations are fetched per variable product (/products?type=variable, then /products/{id}/variations).
  • Status and fallbacks — DB-mode order statuses have the wc- prefix stripped; a missing order status falls back to imported and a missing currency to USD. A variation without a SKU is assigned woo-var-<id>.
  • Orders import as historical records — the default (and currently only working) order_import_mode is historical: order and line rows are written without triggering payment, inventory, or fulfillment workflows, and imported orders are stamped _imported in metadata. Selecting live mode is rejected by the order loader (it raises per row instead of silently re-entering workflows).
  • Order lines must resolve to variants — each order line is matched to an existing variant by SKU (DB mode also falls back to the variation's stamped external id); an unresolvable line fails that order's row. Import products and variants before orders.
  • Idempotent re-runs — external ids are the source's numeric ids (post/term/user/order ids as strings; simple-product default variants use the SKU); each successfully imported row records a (provider, entity, external_id) → internal id crossref. Loaders upsert by natural key — slug for products and collections, SKU for variants, email for customers, order number for orders — so re-running an import updates existing records instead of duplicating them.
  • Failure isolation — every row runs inside a database savepoint, so one bad row cannot poison the rest; failures are collected into the run's error report.
  • Durability — runs execute as the core ImportRunWorkflow on the vectis-main Temporal task queue, with a 30-minute activity timeout and up to 3 retry attempts.

See also: Shopify Import, Building Extensions.