A CRM built around how you actually sell.

Pipelines, properties and permissions mapped to your real sales process, not the default setup.

Six services. One revenue system.

Case study/Data engineering & PIM integration

BODYBILT.

An unmanageable, hand-typed catalog became a 45,000-variant automated e-commerce system.

Six chapters: what BodyBilt sells, why the sync broke, how big the manual workaround really was, what we built, how the naming was resolved, and what shipped.

Client
BodyBilt Seating
Stack
Sales Layer · WooCommerce · Apps Script
Practice
Data governance & automation
Shipped
Dec 2024
01

The client

BodyBilt doesn’t sell products. It sells configurable systems.

They manufacture ergonomic office and industrial seating. Every model can be ordered across dozens of fabric grades, hundreds of fabric colors, several foam types, multiple arm styles, base options, cylinders and caster sets. All of it lived in Sales Layer, a PIM meant to power a WooCommerce storefront automatically.

Seat sizeFabric gradeFabric colorFoamArmsBaseCylinderCastersLumbarMechanismFootring
02

Why the sync broke

The two systems disagreed on what a product is.

In WooCommerce, every buyable configuration is its own variant with a unique SKU. To create that variant on sync, Sales Layer needs one record carrying exactly one value per attribute. BodyBilt’s data wasn’t entered that way — a single row held “Grade 01, 02, 04” in fabric, several bases, a list of cylinders, a set of casters, all at once.

The shape mismatch
What was in the PIM — one row
SKU
A2707
Fabric grade
Grade 01, 02, 04
Foam
High Resiliency, Standard
Bases
B18, B20, B22
Casters
C18, C20, C24
What the sync needs — one row per SKU
A2707-Grade 01 Hue-Black-High Resiliency Foam-APHR/L-B18-Y17-C18
A2707-Grade 01 Hue-Indigo-High Resiliency Foam-APHR/L-B18-Y17-C18
A2707-Grade 02 Momentum-Slate-Standard Foam-APHR/L-B20-Y17-C20
…1,022 more rows, this family alone
In short: Sales Layer can’t expand a multi-value row into many single-SKU variants. Products either failed to appear, or appeared wrong.
03

The size of the workaround

A few hundred products concealed tens of thousands of configurations.

With the automated path broken, the catalog could only be made correct by hand: expand every product into every legitimate combination, write a unique consistent SKU for each, and key it back into Sales Layer one record at a time. Not a task with an end date — a permanent staffing cost that grows with every new fabric.

Aircelli 2700 — full explosionSix attributes, one family
Fabric grade & color ×24Foam ×3Arms ×5Base ×3Cylinder ×2Casters ×4Seat size ×2
Rows to type by hand
8,640
24 × 3 × 5 × 3 × 2 × 4 = 8,640
216 hrs
By hand, at 90s a row
~90 sec
By the pipeline, per run
Counts drawn from the Aircelli 2700 family. The shipped sheet holds 1,026 valid rows for this family once impossible pairings are excluded.
In short: too large to finish, too malformed to trust, too error-prone to repeat.
People were being used as the integration layer between two systems that were supposed to talk to each other
04

What we built

An automated variant-generation pipeline in Google Apps Script. Four parts, in order.

01

Cartesian-product expansion engine

Reads each configurable product, takes every multi-value field, and generates one clean row for every valid combination. What no person could enumerate reliably, the script produces identically on every run.

02

Standardized variant-ID schema

A second script assembles a consistent, readable SKU for every variant — parsing grade and color naming, extracting parenthetical foam codes, and de-duplicating so no two variants collide.

03

Catalog normalization

Before generating anything, the pipeline cleans years of legacy inconsistency so old malformed IDs don’t corrupt the new output.

04

PIM-to-storefront sync

Generated variants were loaded into Sales Layer and synced to WooCommerce, with validation that attributes, variations and pricing rendered as real, orderable products.

Anatomy of a generated ID
SKU
A2707
Fabric grade & color
Grade 01 Hue-Indigo
Foam
High Resiliency Foam
Arms
APHR/L
Base
B18
Cylinder
Y17
Casters
C18
05

Before and after

The naming that made manual work impossible, resolved by rule.

The legacy IDs found in the catalog, then the IDs the pipeline now produces.

Legacy IDs — failure modes
A2707
SKU only
A2707-Grade 01
Grade, no color
a2707-grade01-hue indigo
Case + spacing drift
A2707-Grade 01 Hue-Indigo
Collides ×12
A2707 Grade 1 Hue Indigo HRF B18
No delimiter rule
Generated IDs — collision check
A2707-Grade 01 Chamea-Black-High Resiliency Foam-APHR/L-B18-Y17-C18
Unique
A2707-Grade 01 Hue-Black-High Resiliency Foam-APHR/L-B18-Y17-C18
Unique
A2707-Grade 02 Momentum-Slate-Standard Foam-APHR/L-B20-Y17-C20
Unique
A2707-Grade 04 Origin-Graphite-High Resiliency Foam-APTR/L-B22-Y19-C24
Unique
Format: SKU · fabric grade and color · foam · arms · base · cylinder · casters.
06

A catalog that couldn’t exist, now shipping.

45,000+

Correctly-structured variants across 30 product families.

1,000+

Hours of manual data entry removed, permanently.

Browsable.
Orderable.

No dropped combinations, no mismatched attributes, no pricing drift.

Re-runnable
by design

New products or options regenerate on demand. The catalog grows; the bottleneck doesn’t.

All 30 product families
Aircelli 247
Aircelli 2700 Series
Bariatric Series
Captain 247
Classic 200 Series
Classic 200 Series Stool
Classic 700 Series
Classic 700 Series Stool
Classic 2400 Series
Classic 2500 Series
Classic 3400 Series
Classic 3500 Series
Core 247
Crew 247
Exacti 700 Series
Exacti 700 Stool
GX4 Gaming Chair
GX7 Gaming Chair
Midcelli 247
Midcelli 2800 Series
Midcelli 2800 Mesh Back
NEXT24 2400 Series
NEXT24 2500 Series
NEXT24 2500 Series ST
NEXT24 3400 Intensive Use
NEXT24 3500 Series
Sola 2600 Series ST
Sola 2600 Task Chair
Sola 3600 Executive
SOLA LT Task Chair
Next

Where is your data being typed by hand?

45 minutes, no deck. We map where your CRM, PIM and storefront disagree — and you keep the map whether or not we work together.