skills/customer-crm/user-generated-content/SKILL.md
Let customers upload photos, ask and answer product questions, and share social proof that increases trust and conversion for new visitors
npx skillsauth add finsilabs/awesome-ecommerce-skills user-generated-contentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
User-generated content (UGC) — customer photos, Q&A, and recent purchase signals — increases product page conversion by giving new visitors authentic proof that real customers use and love the product. Review apps like Judge.me and Yotpo include UGC photo collection, product Q&A, and social proof widgets out of the box. Dedicated UGC platforms like Okendo, Loox, and Bazaarvoice provide more advanced sourcing from Instagram and TikTok. Only build a custom UGC pipeline if your moderation logic, rights management, or database requirements exceed what these tools support.
| Platform | Recommended Tool | Why | |----------|-----------------|-----| | Shopify | Loox | Focused on photo reviews — sends post-purchase emails requesting photo uploads, displays a visual gallery on the PDP | | Shopify | Okendo | Full UGC suite: photo + video reviews, Q&A, attributes ratings (sizing, quality), Instagram UGC sourcing | | Shopify | Judge.me | Includes photo reviews in the free plan; good starting point before moving to Loox or Okendo | | WooCommerce | WooCommerce built-in reviews + WP Product Review | Handles text + star reviews; add a media upload plugin for photo submissions | | WooCommerce | Yotpo for WooCommerce | Full UGC including photo reviews, Q&A, and social proof | | BigCommerce | Okendo or Yotpo | Both available on the BigCommerce App Marketplace | | Custom / Headless | Build UGC API with S3 + moderation | Required when UGC data needs to live in your own database or integrate with a custom recommendation engine |
Option A: Loox (recommended for photo-first UGC)
Option B: Okendo (full UGC including Q&A and Instagram)
Social proof notification widget (Loox and Okendo): Both apps include a "recent purchases" pop-up widget. Enable it in Loox → Widgets → Social Proof or Okendo → Widgets → Purchase Activity. Configure to show on product and collection pages, not on checkout.
Yotpo for WooCommerce:
WooCommerce native reviews + photo upload:
Sourcing Instagram UGC: Use Tagembed or Smash Balloon Instagram Feed (WordPress plugins) to embed tagged Instagram posts on product pages. These pull posts by hashtag or mentions and let you curate which appear on each product.
Okendo for BigCommerce:
Yotpo for BigCommerce:
For headless storefronts needing UGC in your own database:
// lib/ugc.ts
// Generate a presigned S3 URL for direct browser-to-S3 photo uploads
// Never route file uploads through your API server
export async function getPhotoUploadUrl(req: Request, res: Response) {
const { productId, fileName, contentType } = req.body;
if (!['image/jpeg', 'image/png', 'image/webp'].includes(contentType)) {
return res.status(400).json({ error: 'Only JPEG, PNG, and WebP accepted' });
}
const key = `ugc/pending/${productId}/${req.session.customerId}/${Date.now()}-${fileName}`;
const command = new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key, ContentType: contentType });
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
await db.ugcPhotos.create({ data: { productId, customerId: req.session.customerId, s3Key: key, status: 'pending_upload' } });
res.json({ uploadUrl, key });
}
// S3 ObjectCreated trigger: run content moderation then resize
export async function processUGCPhoto(s3Key: string) {
const ugcRecord = await db.ugcPhotos.findFirst({ where: { s3Key } });
if (!ugcRecord) return;
// AWS Rekognition content moderation — auto-reject NSFW content
const moderationResult = await rekognition.detectModerationLabels({
Image: { S3Object: { Bucket: process.env.S3_BUCKET!, Name: s3Key } },
MinConfidence: 75,
});
if ((moderationResult.ModerationLabels ?? []).length > 0) {
await db.ugcPhotos.update({ where: { id: ugcRecord.id }, data: { status: 'rejected' } });
return;
}
// Resize to thumbnail (200px), medium (600px), full (1200px) WebP variants
const original = await s3.getObject({ Bucket: process.env.S3_BUCKET!, Key: s3Key });
const buffer = Buffer.from(await original.Body!.transformToByteArray());
for (const { suffix, width } of [{ suffix: 'thumbnail', width: 200 }, { suffix: 'medium', width: 600 }, { suffix: 'full', width: 1200 }]) {
const resized = await sharp(buffer).resize(width).webp({ quality: 80 }).toBuffer();
await s3.putObject({
Bucket: process.env.S3_BUCKET!, Body: resized, ContentType: 'image/webp',
Key: `ugc/approved/${ugcRecord.productId}/${ugcRecord.id}/${suffix}.webp`,
});
}
// Auto-approve verified purchase photos; queue others for manual review
const isVerifiedPurchase = await db.orderItems.findFirst({ where: { customerId: ugcRecord.customerId, productId: ugcRecord.productId } });
await db.ugcPhotos.update({ where: { id: ugcRecord.id }, data: {
status: isVerifiedPurchase ? 'approved' : 'pending_review',
verifiedPurchase: !!isVerifiedPurchase,
}});
}
// Product Q&A — submit and answer questions
export async function submitQuestion(req: Request, res: Response) {
const { productId, question, authorName } = req.body;
const q = await db.productQuestions.create({ data: { productId, question, authorName, authorEmail: req.session.customerEmail, status: 'pending' } });
await notifyProductTeam({ questionId: q.id, productId, question });
res.json({ questionId: q.id });
}
// Social proof feed: recent purchases cached for 5 minutes
export async function buildRecentPurchaseFeed() {
const recentOrders = await db.orders.findMany({
where: { createdAt: { gte: new Date(Date.now() - 86400000) }, status: 'completed' },
include: { lineItems: { include: { product: true } }, customer: true },
orderBy: { createdAt: 'desc' },
take: 100,
});
const feed = recentOrders.flatMap(order =>
order.lineItems.slice(0, 1).map(item => ({
productId: item.productId,
productName: item.product.name,
buyerFirstName: order.customer.firstName,
buyerLocation: order.customer.city ?? 'somewhere',
purchasedAt: order.createdAt.toISOString(),
}))
);
await redis.setex('social_proof_feed', 300, JSON.stringify(feed));
}
Before displaying any customer social media content on your store or in paid ads:
Unanswered questions damage trust more than having no Q&A at all:
In Okendo and Yotpo:
Best practice: Enable customer-to-customer answers (other buyers can answer) — this reduces the staff burden and customers often provide more authentic answers than support agents.
| Metric | Benchmark | Where to Find | |--------|-----------|---------------| | UGC photo submission rate | 2–5% of delivered orders | Loox / Okendo email analytics | | Products with 3+ customer photos | Track % of top 50 products | App dashboard | | Q&A answer rate | 90%+ within 7 days | Okendo / Yotpo Q&A report | | Social proof widget click-through | 1–3% of product page visitors | Widget analytics in Loox/Okendo |
| Problem | Solution | |---------|----------| | Inappropriate content appears on product pages | Enable content moderation before approval; Loox and Okendo run automated screening automatically | | Social proof widget shows stale or fake urgency | Use a 5-minute cache and only show genuine purchase signals — fabricating data creates reputational and legal risk | | Q&A section has unanswered questions for weeks | Send a daily digest to the product team; questions older than 7 days without a staff answer damage trust more than no Q&A | | UGC photos slow page LCP | Loox and Okendo serve via CDN with automatic resizing; for custom builds, always resize to 200px thumbnails and use lazy loading | | Instagram posts used in paid ads without rights | Track rights approval status per post; Okendo handles this automatically — never publish to ads without confirmed consent |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion