
Product Information Management
- 62 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Centralize product data in a PIM like Akeneo or Salsify and syndicate enriched content to all sales channels automatically.
About
Centralizes product data in a PIM system (Akeneo, Salsify) and syndicates enriched content across all sales channels. A developer uses it to keep product information consistent across many channels.
- Centralized product data in Akeneo or Salsify
- Automatic content syndication to all channels
Product Information Management by the numbers
- 62 all-time installs (skills.sh)
- Ranked #3,145 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill product-information-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Centralize product data in a PIM like Akeneo or Salsify and syndicate enriched content to all sales channels automatically.
Files
Product Information Management
Overview
A Product Information Management (PIM) system is the single source of truth for product data — names, descriptions, images, attributes, and digital assets — across all channels (website, marketplaces, print catalogs). Akeneo and Salsify are the dominant PIM platforms. This skill covers connecting a PIM as the authoritative source for product enrichment, implementing sync between the PIM and your commerce platform, and building a pipeline that transforms PIM data into channel-specific formats.
When to Use This Skill
- When product data is inconsistent across your website, marketplace listings, and internal systems
- When the merchandising team manages product content in a PIM and the commerce platform needs to reflect it
- When building a new headless storefront that needs a source of enriched product data
- When adding a new sales channel (marketplace, B2B portal) that needs channel-specific product data
- When auditing product data quality and identifying missing attributes across the catalog
Core Instructions
Step 1: Determine your platform and PIM integration approach
| Platform | PIM Integration Option | What It Syncs |
|---|---|---|
| Shopify | Akeneo's official Shopify connector (free, Akeneo Marketplace) or Salsify Syndication for Shopify | Product names, descriptions, images, and attributes → Shopify product metafields; Shopify variants mapped to Akeneo product models |
| WooCommerce | Akeneo WooCommerce Connector (open-source, GitHub) or custom REST API sync | Product data pushed to WooCommerce via the Products REST API; images uploaded to WordPress media library |
| BigCommerce | Akeneo BigCommerce Connector (Akeneo Marketplace) or Salsify for BigCommerce | Product attributes pushed to BigCommerce custom fields and variants; image syndication to BigCommerce CDN |
| Custom / Headless | Direct REST API integration with Akeneo or Salsify | Full control over data mapping; incremental sync via updated filter; image upload to your CDN during sync |
Step 2: Platform-specific PIM integration
---
Shopify
Connect Akeneo to Shopify using the official connector:
1. In your Akeneo instance, go to Connect → Marketplace and install the Shopify Connector (free, by Akeneo) 2. Configure a Connection in Akeneo (under Connect → Connections) with read permissions for Products, Media files, and Attribute options 3. In the connector settings, map your Akeneo channels/locales to your Shopify markets (e.g., Akeneo en_US scope → Shopify default language) 4. Map Akeneo attribute codes to Shopify fields:
name→ Shopify product titledescription→ Shopify body HTMLprice→ Shopify variant price- Custom attributes → Shopify product metafields (configure under Settings → Custom data in Shopify admin)
5. Run an initial full sync and then schedule incremental syncs — the connector pulls only products with updated > last_sync_at
Verify the sync:
- Go to a product in your Shopify admin and check that the title, description, and images match what's in Akeneo
- Check metafields in the Shopify product page under Metafields section
---
WooCommerce
Sync Akeneo to WooCommerce using the REST API:
The open-source Akeneo WooCommerce Connector (available at github.com/akeneo/woocommerce-connector) provides a starting point, but many merchants build a custom sync script:
1. Set up a cron job (daily or hourly) that:
- Fetches products from Akeneo updated since the last sync using
GET /api/rest/v1/products?search={"updated":[{"operator":">","value":"..."}]} - Checks if the product exists in WooCommerce using
GET /wp-json/wc/v3/products?sku={sku} - Creates or updates the WooCommerce product using
POSTorPUT /wp-json/wc/v3/products/{id}
2. Map Akeneo fields to WooCommerce fields:
- Akeneo
name(en_US) → WooCommercename - Akeneo
description→ WooCommercedescription - Akeneo
images→ Download and upload to WordPress media library, then set as WooCommerce product images - Akeneo custom attributes → WooCommerce product attributes or ACF custom fields
3. After each sync, clear WooCommerce's transient cache: wp transient delete-all via WP-CLI to ensure updated products appear immediately
---
BigCommerce
Connect Akeneo using the BigCommerce connector:
1. In Akeneo Marketplace, install the BigCommerce Connector and configure your BigCommerce API credentials (Client ID, Client Secret, Access Token from Advanced Settings → API Accounts) 2. Map Akeneo families to BigCommerce product types in the connector configuration 3. Configure attribute mappings:
- Akeneo attributes → BigCommerce custom fields or variant option sets
- Akeneo categories → BigCommerce category tree
4. Schedule regular incremental syncs from the connector settings
---
Custom / Headless
Connect to the Akeneo REST API:
// lib/akeneo/client.ts
export class AkeneoClient {
private accessToken: string | null = null;
private tokenExpiry: number = 0;
constructor(private config: {
baseUrl: string; clientId: string; clientSecret: string;
username: string; password: string;
}) {}
async getToken(): Promise<string> {
if (this.accessToken && this.tokenExpiry > Date.now() + 60000) return this.accessToken;
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
const res = await fetch(`${this.config.baseUrl}/api/oauth/v1/token`, {
method: 'POST',
headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'password', username: this.config.username, password: this.config.password }),
});
const data = await res.json();
this.accessToken = data.access_token;
this.tokenExpiry = Date.now() + data.expires_in * 1000;
return this.accessToken!;
}
async getAll(path: string): Promise<any[]> {
const items: any[] = [];
let nextUrl: string | null = path;
while (nextUrl) {
const token = await this.getToken();
const res = await fetch(`${this.config.baseUrl}${nextUrl}`, {
headers: { 'Authorization': `Bearer ${token}` },
});
const page = await res.json();
items.push(...(page._embedded?.items ?? []));
nextUrl = page._links?.next?.href?.replace(this.config.baseUrl, '') ?? null;
}
return items;
}
}
export const akeneo = new AkeneoClient({
baseUrl: process.env.AKENEO_BASE_URL!,
clientId: process.env.AKENEO_CLIENT_ID!,
clientSecret: process.env.AKENEO_CLIENT_SECRET!,
username: process.env.AKENEO_USERNAME!,
password: process.env.AKENEO_PASSWORD!,
});Transform Akeneo's locale/scope-scoped attribute format into a flat storefront product:
// lib/akeneo/product-transformer.ts
export function transformAkeneoProduct(akeneoProduct: any, locale = 'en_US', scope = 'ecommerce') {
const getValue = (attrCode: string, defaultValue: any = null) => {
const values = akeneoProduct.values[attrCode] ?? [];
const match = values.find(v => v.locale === locale && v.scope === scope)
?? values.find(v => v.locale === locale && v.scope === null)
?? values.find(v => v.locale === null && v.scope === scope)
?? values.find(v => v.locale === null && v.scope === null);
return match?.data ?? defaultValue;
};
return {
sku: akeneoProduct.identifier,
name: getValue('name', '') as string,
description: getValue('description', '') as string,
brand: getValue('brand', '') as string,
categories: akeneoProduct.categories,
attributes: {
color: getValue('color'),
size: getValue('size'),
material: getValue('material'),
},
enabled: akeneoProduct.enabled,
};
}Incremental sync job (fetch only updated products):
// jobs/akeneo-sync.ts
export async function syncAkeneoProducts() {
const lastSyncAt = await db.syncState.getLastSync('akeneo_products');
const syncStartTime = new Date();
const updatedAt = lastSyncAt?.toISOString() ?? '2020-01-01T00:00:00+00:00';
const products = await akeneo.getAll(
`/api/rest/v1/products?search={"updated":[{"operator":">","value":"${updatedAt}"}]}&limit=100&with_attribute_options=true`
);
let synced = 0, errors = 0;
for (const akeneoProduct of products) {
try {
const storefrontProduct = transformAkeneoProduct(akeneoProduct);
// Validate required fields before upserting
if (!storefrontProduct.name) {
console.warn(`Skipping ${akeneoProduct.identifier}: missing name`);
continue;
}
await db.products.upsert(storefrontProduct.sku, {
...storefrontProduct,
akeneoUpdatedAt: new Date(akeneoProduct.updated),
});
synced++;
} catch (err: any) {
errors++;
await db.syncErrors.insert({ productId: akeneoProduct.identifier, error: err.message });
}
}
await db.syncState.updateLastSync('akeneo_products', syncStartTime);
console.log(`Akeneo sync complete: ${synced} synced, ${errors} errors`);
}Webhook-triggered sync when Akeneo publishes a product (Akeneo's Event API):
// Register the webhook endpoint in Akeneo under: Connect → Webhooks
// POST /api/webhooks/akeneo
export async function POST(req: NextRequest) {
const event = await req.json();
if (event.event_type === 'product.updated' || event.event_type === 'product.created') {
const sku = event.data.resource.identifier;
const akeneoProduct = await akeneo.getAll(`/api/rest/v1/products/${sku}?with_attribute_options=true`);
const storefrontProduct = transformAkeneoProduct(akeneoProduct);
await db.products.upsert(sku, storefrontProduct);
// Purge CDN cache for this product's page
await revalidateProductPage(storefrontProduct.slug);
}
return NextResponse.json({ received: true });
}Best Practices
- Treat the PIM as the source of truth — never write product content back from commerce to PIM — data flows from PIM to commerce; only push back to PIM for data the PIM explicitly manages (e.g., SEO metadata your platform generates)
- Use incremental sync, not full sync — fetching all products every 15 minutes is expensive; use Akeneo's
updatedfilter to fetch only changed products - Upload images to your own CDN during sync — Akeneo media file URLs are internal API URLs requiring authentication; never serve them directly to customers; upload to S3/R2/Cloudinary during sync
- Cache attribute options locally — color, size, and material option label lookups change rarely; cache them in Redis and refresh hourly to avoid per-product API calls
- Validate required attributes before syncing to the storefront — a product without a name or primary image should not be published; add validation before upsert
Common Pitfalls
| Problem | Solution |
|---|---|
| Sync fails on products with missing required attributes | Wrap each product sync in try/catch; log the product identifier with the error; skip and continue rather than aborting the entire sync |
| Images not available after sync | Akeneo media URLs require authentication; download and re-upload to your CDN during sync — never serve akeneo-base-url/api/rest/v1/media-files/... directly |
| Akeneo API rate limits | Use batched requests and run syncs off-peak; cache attribute options locally to reduce API calls per product |
| Category mapping out of sync after PIM reorganization | Build a category sync job that runs before the product sync; alert when an Akeneo category code has no mapping in your commerce platform |
| Shopify connector not syncing metafields | Ensure the metafield namespace and key in the connector configuration match what's configured in Shopify admin → Settings → Custom data → Products |
Related Skills
- @marketplace-connectors
- @webhook-architecture
- @erp-integration
- @analytics-integration
{
"context": "Tests whether the agent correctly implements the Akeneo OAuth2 client credentials flow with token caching, uses the proper pagination pattern for Akeneo's REST API, implements incremental sync using the updated filter, and builds a resilient per-product error isolation pattern.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Basic Auth encoding",
"max_score": 8,
"description": "The token request uses Basic Auth with base64-encoded clientId:clientSecret in the Authorization header (Buffer.from(`${clientId}:${clientSecret}`).toString('base64') or equivalent)"
},
{
"name": "grant_type password",
"max_score": 8,
"description": "The token request body includes grant_type: 'password' along with username and password fields"
},
{
"name": "Token expiry buffer",
"max_score": 8,
"description": "Token cache check uses a buffer before expiry (e.g., tokenExpiry > Date.now() + 60000 or similar positive offset) rather than checking exact expiry"
},
{
"name": "Token expiry calculation",
"max_score": 6,
"description": "Token expiry is calculated as Date.now() + expires_in * 1000 (or equivalent ms conversion from seconds)"
},
{
"name": "Pagination via _embedded.items",
"max_score": 8,
"description": "Paginated getAll uses page._embedded?.items (or _embedded.items) to extract results from each page response"
},
{
"name": "Pagination next link",
"max_score": 8,
"description": "Paginated getAll follows _links.next.href (stripping the base URL) to advance to the next page"
},
{
"name": "Incremental sync filter",
"max_score": 10,
"description": "The sync job uses an 'updated' search filter with operator '>' and a stored ISO timestamp (e.g., search={\"updated\":[{\"operator\":\">\",\"value\":\"...\"}]})"
},
{
"name": "with_attribute_options param",
"max_score": 6,
"description": "Product fetch requests include with_attribute_options=true as a query parameter"
},
{
"name": "Per-product error isolation",
"max_score": 10,
"description": "Each product's processing is wrapped in a try/catch so that a failure on one product does not stop processing of remaining products"
},
{
"name": "Error logging with identifier",
"max_score": 8,
"description": "Caught errors are logged with the product's identifier (SKU/identifier field) included in the error message or log entry"
},
{
"name": "akeneoUpdatedAt stored",
"max_score": 8,
"description": "The upsert/save operation includes the Akeneo product's 'updated' timestamp stored as a separate field (e.g., akeneoUpdatedAt)"
},
{
"name": "File structure",
"max_score": 6,
"description": "Client is placed in lib/akeneo/client.ts and sync job in jobs/akeneo-sync.ts (or a close equivalent following the same convention)"
},
{
"name": "Sync state persistence",
"max_score": 6,
"description": "The sync job reads a last-sync timestamp before fetching and updates it after a successful sync run"
}
]
}
Akeneo PIM Sync Integration
Problem/Feature Description
A growing e-commerce company has just licensed Akeneo PIM as their central hub for product content. The engineering team needs to build the foundational TypeScript integration layer that connects their Node.js backend to Akeneo, so that product data flows automatically into their commerce database whenever merchandisers update products in Akeneo.
The team currently does a full catalog export every night (all 20,000 products), which takes 45 minutes and hammers the Akeneo API. They need a new approach that only fetches products changed since the last run. The integration also needs to be resilient — a single bad product record shouldn't abort the whole job and lose updates for the rest of the catalog. The last-sync timestamp should be persisted between runs so the job can always pick up where it left off.
Output Specification
Produce a TypeScript implementation with the following files:
lib/akeneo/client.ts— Akeneo API client classjobs/akeneo-sync.ts— Scheduled sync job
The implementation should be realistic, runnable TypeScript (no pseudo-code). Use environment variables for all credentials and configuration. Add a sync-log.md documenting how the key design decisions in the implementation work — specifically the authentication approach, pagination strategy, and how incremental sync is achieved.
{
"context": "Tests whether the agent correctly implements the Akeneo attribute value locale/scope fallback priority, caches attribute option codes as a Map for label lookup, uses labels instead of raw option codes, validates required fields before publishing, and applies Akeneo completeness scores as a quality gate.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Locale+scope first fallback",
"max_score": 9,
"description": "Attribute value resolution tries locale+scope match first before falling back to less specific matches"
},
{
"name": "Four-level fallback chain",
"max_score": 9,
"description": "Fallback priority implements all four levels: locale+scope, then locale-only (scope null), then scope-only (locale null), then neither (both null)"
},
{
"name": "Attribute options as Map",
"max_score": 9,
"description": "Attribute options are stored in a Map (or equivalent key-value cache) indexed by attribute code, with values as a Map from option code to label string"
},
{
"name": "Labels used not codes",
"max_score": 9,
"description": "Transformed product output contains human-readable labels (e.g., 'Blue', 'Medium') rather than raw option codes (e.g., 'CLR_BLU', 'SZ_M') for select-list attributes"
},
{
"name": "Option code fallback",
"max_score": 6,
"description": "When no label is found for an option code, the code itself is returned as fallback rather than null or an error"
},
{
"name": "Name validation",
"max_score": 9,
"description": "Products with a missing or empty name field are rejected/filtered out before reaching the storefront output"
},
{
"name": "Image validation",
"max_score": 9,
"description": "Products without a primary image are rejected/filtered out before reaching the storefront output"
},
{
"name": "Completeness gate",
"max_score": 10,
"description": "Only products with 100% completeness for the ecommerce channel are passed through (products with <100% ecommerce completeness are excluded)"
},
{
"name": "Design doc explains fallback",
"max_score": 8,
"description": "pipeline-design.md (or equivalent documentation file) explains the locale/scope fallback priority order"
},
{
"name": "Design doc explains caching",
"max_score": 8,
"description": "pipeline-design.md explains how attribute option labels are loaded and cached"
},
{
"name": "Transformer file structure",
"max_score": 7,
"description": "Transformation logic is placed in lib/akeneo/product-transformer.ts (or equivalent path following the lib/akeneo/ convention)"
},
{
"name": "Options loader separate",
"max_score": 7,
"description": "Attribute option loading/caching logic is separated from the transformation logic (in a distinct file or function)"
}
]
}
Product Enrichment Pipeline for Headless Storefront
Problem/Feature Description
A fashion retailer is launching a new headless storefront backed by Akeneo PIM. Their catalog has attributes like color, size, material, and country_of_origin stored as select-list option codes (e.g., color: "CLR_BLU" instead of "Blue"). They also have localized attributes — descriptions exist in multiple locales and are scoped to different sales channels. Without correct transformation, the storefront will display cryptic codes instead of readable labels, and products missing key content will slip through to production.
The team needs a TypeScript data pipeline that takes raw Akeneo product payloads and transforms them into clean storefront-ready objects. The pipeline must resolve attribute option codes to human-readable labels (loading options from Akeneo and caching them), flatten localized/scoped attribute values correctly, validate that products meet minimum content requirements before passing them downstream, and filter out incomplete products using Akeneo's own completeness data.
Output Specification
Produce a TypeScript implementation with the following files:
lib/akeneo/product-transformer.ts— transformation logic including locale/scope resolutionlib/akeneo/attribute-options.ts— attribute option loader and label resolverlib/akeneo/pipeline.ts— the pipeline that loads options, validates products, and transforms them
Include a pipeline-design.md that explains the key design decisions in your pipeline: how multi-locale/multi-scope attribute values are resolved, how attribute option labels are loaded and made available to the transformer, and what quality checks are applied before a product is passed downstream.
Input Files
The following representative Akeneo API payloads are provided as reference data. Extract them before beginning.
=============== FILE: inputs/sample-products.json =============== [ { "identifier": "SHIRT-001", "family": "shirts", "enabled": true, "categories": ["summer_collection", "mens"], "updated": "2026-03-10T14:00:00+00:00", "values": { "name": [ {"locale": "en_US", "scope": null, "data": "Classic Oxford Shirt"}, {"locale": "fr_FR", "scope": null, "data": "Chemise Oxford Classique"} ], "description": [ {"locale": "en_US", "scope": "ecommerce", "data": "A timeless Oxford shirt crafted from premium cotton."}, {"locale": "en_US", "scope": "print", "data": "Premium Oxford shirt for print catalog."} ], "color": [{"locale": null, "scope": null, "data": "CLR_BLU"}], "size": [{"locale": null, "scope": null, "data": "SZ_M"}], "material": [{"locale": null, "scope": null, "data": "MAT_COT"}], "images": [{"locale": null, "scope": null, "data": "abc123.jpg"}] }, "completeness": {"ecommerce": 100, "print": 85} }, { "identifier": "SHIRT-002", "family": "shirts", "enabled": true, "categories": ["summer_collection"], "updated": "2026-03-11T09:00:00+00:00", "values": { "name": [{"locale": "en_US", "scope": null, "data": ""}], "description": [{"locale": "en_US", "scope": "ecommerce", "data": "A lightweight linen shirt."}], "color": [{"locale": null, "scope": null, "data": "CLR_WHT"}], "size": [{"locale": null, "scope": null, "data": "SZ_L"}] }, "completeness": {"ecommerce": 60, "print": 40} } ]
=============== FILE: inputs/sample-attribute-options.json =============== { "color": [ {"code": "CLR_BLU", "labels": {"en_US": "Blue", "fr_FR": "Bleu"}}, {"code": "CLR_WHT", "labels": {"en_US": "White", "fr_FR": "Blanc"}} ], "size": [ {"code": "SZ_M", "labels": {"en_US": "Medium"}}, {"code": "SZ_L", "labels": {"en_US": "Large"}} ], "material": [ {"code": "MAT_COT", "labels": {"en_US": "Cotton", "fr_FR": "Coton"}} ] }
{
"context": "Tests whether the agent uses the correct Salsify API authentication pattern, maps Salsify's plain-English property names to normalized internal fields including Digital Assets filtering, and builds a data quality report that sorts by lowest completeness first.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Salsify Bearer token auth",
"max_score": 10,
"description": "Salsify API requests use 'Authorization: Bearer <token>' header with the API key (from environment variable SALSIFY_API_KEY or similar)"
},
{
"name": "Product ID mapping",
"max_score": 9,
"description": "The mapper uses p['Product ID'] ?? p.id (or equivalent fallback) for the sku field — not just p.id alone"
},
{
"name": "Name and description mapping",
"max_score": 8,
"description": "Mapper maps 'Product Name' to the name field and 'Long Description' to the description field"
},
{
"name": "Brand mapping",
"max_score": 6,
"description": "Mapper maps 'Brand' to a brand field in the normalized output"
},
{
"name": "Image filter on Digital Assets",
"max_score": 10,
"description": "Images are extracted from 'Digital Assets' by filtering for items where type === 'Image' (not all assets, not Documents)"
},
{
"name": "Non-core attributes preserved",
"max_score": 8,
"description": "Remaining properties not in the explicitly mapped fields (Product Name, Long Description, Brand, Digital Assets) are preserved in an 'attributes' object"
},
{
"name": "Quality report per attribute",
"max_score": 9,
"description": "Data quality report includes an entry for each required attribute in the family showing count of products with that attribute filled vs. total"
},
{
"name": "Completeness percentage",
"max_score": 8,
"description": "Report includes a completeness percentage for each attribute (filled/total * 100)"
},
{
"name": "Sorted lowest first",
"max_score": 10,
"description": "Quality report results are sorted with lowest completeness attributes first (ascending by filled count or completeness percentage)"
},
{
"name": "quality-report.json generated",
"max_score": 8,
"description": "A quality-report.json file is produced with the report results based on the provided sample data"
},
{
"name": "Migration notes auth section",
"max_score": 7,
"description": "migration-notes.md (or equivalent documentation) includes a section explaining how Salsify authentication works"
},
{
"name": "Migration notes sorting explanation",
"max_score": 7,
"description": "migration-notes.md explains that the quality report is sorted to show worst gaps (lowest completeness) first"
}
]
}
Salsify Catalog Migration and Data Quality Dashboard
Problem/Feature Description
A home goods brand is migrating their product catalog management from Salsify to a new commerce platform. Their Salsify instance holds 3,000+ products with properties named in plain English (e.g., "Product Name", "Long Description", "Brand") and digital assets organized by type. The migration team has discovered that data quality is inconsistent — many products are missing required attributes for certain product families — and they need to surface these gaps before going live.
The engineering team needs two things: (1) a TypeScript integration that fetches products from Salsify and normalizes them into the same internal product shape used by the rest of the platform, and (2) a data quality reporting tool that reads a snapshot of the product catalog (in the same Salsify response format) and produces a completeness report showing, for each required attribute in a given product family, how many products have it filled in — sorted to highlight the worst gaps first.
Output Specification
Produce a TypeScript implementation with the following files:
lib/salsify/client.ts— Salsify API client with product fetchinglib/salsify/product-mapper.ts— mapping Salsify property names to normalized internal fieldslib/quality/data-quality.ts— data quality report generatorquality-report.json— the data quality report generated by running the tool against the provided sample data
Include a migration-notes.md explaining: how Salsify authentication works, how products are mapped from Salsify's property naming convention to the internal format, and how the quality report sorts its results.
Input Files
The following sample data is provided. Extract it before beginning.
=============== FILE: inputs/salsify-products.json =============== [ { "id": "prod-001", "Product ID": "HG-BOWL-001", "Product Name": "Ceramic Serving Bowl", "Long Description": "Hand-crafted ceramic serving bowl with a matte finish, perfect for entertaining.", "Brand": "ArtisanHome", "Color": "Sage Green", "Material": "Ceramic", "Weight (lbs)": 2.4, "Digital Assets": [ {"name": "front-view.jpg", "url": "https://cdn.example.com/hg-bowl-001-front.jpg", "type": "Image"}, {"name": "spec-sheet.pdf", "url": "https://cdn.example.com/hg-bowl-001-spec.pdf", "type": "Document"} ], "system_updated_at": "2026-03-10T10:00:00Z" }, { "id": "prod-002", "Product ID": "HG-VASE-001", "Product Name": "Tall Glass Vase", "Long Description": "", "Brand": "", "Color": "Clear", "Digital Assets": [], "system_updated_at": "2026-03-11T08:30:00Z" }, { "id": "prod-003", "Product ID": "HG-PLATE-001", "Product Name": "Dinner Plate Set", "Long Description": "Set of 4 hand-painted dinner plates.", "Brand": "ArtisanHome", "Material": "Ceramic", "Weight (lbs)": 3.1, "Digital Assets": [ {"name": "plate-set.jpg", "url": "https://cdn.example.com/hg-plate-001.jpg", "type": "Image"} ], "system_updated_at": "2026-03-09T16:45:00Z" } ]
=============== FILE: inputs/family-attributes.json =============== { "home_goods": ["Product Name", "Long Description", "Brand", "Color", "Material", "Weight (lbs)", "Digital Assets"] }
{
"name": "finsi/product-information-management",
"version": "0.1.0",
"summary": "PIM integration (Akeneo, Salsify) for centralized product data",
"skills": {
"product-information-management": {
"path": "SKILL.md"
}
}
}