
Customer Accounts
- 61 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Let shoppers register, manage their profile, save multiple addresses, and view order history using the platform's built-in customer account system.
About
A skill for setting up ecommerce customer accounts with registration, profiles, address books, and order history via built-in platform features. A developer uses it to enable accounts and decide whether to make them optional or required.
- Built-in account, address book, and order history per platform
- Decisions on optional vs required accounts and extending pages
Customer Accounts by the numbers
- 61 all-time installs (skills.sh)
- Ranked #3,152 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 customer-accountsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 61 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Let shoppers register, manage their profile, save multiple addresses, and view order history using the platform's built-in customer account system.
Files
Customer Accounts
Overview
Customer accounts let shoppers save addresses for faster checkout, view order history, track shipments, and manage their profile. Every major e-commerce platform has this built in — Shopify, WooCommerce, and BigCommerce all provide account registration, login, address books, and order history without any custom development. The main decisions are: whether to make accounts optional or required, and whether to extend the platform's default account pages with additional functionality.
When to Use This Skill
- When enabling customer registration and login on a new storefront
- When customizing the account dashboard with order history and tracking
- When adding an address book to speed up returning customer checkout
- When converting guest checkout users into registered customers
- When adding wishlist or saved-for-later functionality
Core Instructions
Step 1: Determine platform and enable customer accounts
| Platform | Account System | Recommended Extension |
|---|---|---|
| Shopify | Native customer accounts (classic or new) | Customer Accounts Concierge or Flits for enhanced account pages |
| WooCommerce | My Account page (built-in, customizable) | YITH WooCommerce Wishlist; WooCommerce Memberships for gated content |
| BigCommerce | Customer account portal (built-in) | Customer Groups for tiered access; LoyaltyLion for loyalty integration |
| Custom / Headless | Build with JWT sessions and bcrypt password hashing | Required for complete control over authentication and account UX |
---
Step 2: Platform-specific setup
---
Shopify
Shopify offers two account experiences: Classic accounts and the newer New customer accounts (available on all plans).
Enable accounts and choose the experience:
1. Go to Settings → Customer accounts 2. Choose between:
- Classic accounts: traditional email + password login with customizable account pages via Liquid theme
- New customer accounts: passwordless login via email link; Shopify-hosted pages that Shopify controls (faster to set up, less customizable)
3. Set accounts as Optional (recommended) or Required
Classic accounts setup:
1. Enable Classic accounts in Settings → Customer accounts 2. The account page appears at /account — your theme controls the layout 3. Customize the account page in Online Store → Themes → Customize → Customer account pages 4. Key pages: Login, Register, Account overview, Order detail, Addresses
Making accounts optional (strongly recommended):
- Always allow guest checkout — never force registration before purchase
- After checkout, show a "Create account to save your details" prompt
- Shopify handles the "convert guest to account" flow automatically when a customer registers with the same email used for a past order
Extending account pages:
For enhanced account functionality (wishlist, loyalty points, social login, recent orders with tracking):
- Install Flits from the App Store — comprehensive account page customization
- Or install Customer Accounts Concierge — adds wishlist, recently viewed, reorder functionality
---
WooCommerce
WooCommerce has a built-in My Account page that includes order history, addresses, and profile management.
Set up My Account:
1. Go to WooCommerce → Settings → Accounts & Privacy 2. Configure:
- Guest checkout: check "Allow customers to place orders without an account"
- Account creation: optionally auto-create accounts during checkout
- Account erasure: enable "Allow customers to request account deletion"
3. The My Account page is created automatically at /my-account/
Customize My Account tabs:
The default tabs are: Dashboard, Orders, Downloads, Addresses, Account details, Logout. Add or remove tabs: 1. Add custom tabs by using the woocommerce_account_menu_items filter in your child theme's functions.php, or install a plugin like YITH WooCommerce Customize My Account Page 2. Reorder tabs by modifying the array in the filter
Address book:
1. Go to WooCommerce → Settings → Accounts → Allow customers to store multiple addresses 2. Customers can add/edit multiple addresses in My Account → Addresses 3. WooCommerce pre-fills checkout with the default address automatically
Wishlist:
- Install YITH WooCommerce Wishlist (free/premium)
- Adds a "Add to Wishlist" button on product pages and a wishlist page in My Account
Converting guest to registered after purchase:
WooCommerce shows a "Create account" prompt in order confirmation emails automatically when accounts are enabled but optional.
---
BigCommerce
BigCommerce has a built-in customer portal.
Enable and configure:
1. Go to Store Setup → Store Settings → Display → Customer Account Access 2. Set to "Optional" to allow guest checkout 3. Account pages are managed by your theme — customize in the Stencil theme editor
Customer groups:
Use customer groups for tiered access, B2B pricing, or member-only categories: 1. Go to Customers → Customer Groups → Add Group 2. Set group-specific pricing, category visibility, or shipping rules 3. Assign customers to groups manually or auto-assign based on purchase history
Address book:
- Built-in under the customer account portal
- Customers can save multiple addresses and select them at checkout
---
Custom / Headless
For headless storefronts, build a complete account system with secure authentication:
// lib/auth.ts
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { z } from 'zod';
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
firstName: z.string().min(1).max(100),
lastName: z.string().min(1).max(100),
acceptsMarketing: z.boolean().default(false),
});
// POST /api/customers/register
export async function register(req: Request, res: Response) {
const input = registerSchema.parse(req.body);
const existing = await db.customers.findByEmail(input.email.toLowerCase());
if (existing) return res.status(409).json({ error: 'An account with this email already exists' });
const passwordHash = await bcrypt.hash(input.password, 12); // Cost factor 12 minimum
const customer = await db.customers.create({ ...input, email: input.email.toLowerCase(), passwordHash });
await sendVerificationEmail(customer);
const token = jwt.sign({ sub: customer.id, type: 'customer' }, process.env.JWT_SECRET!, { expiresIn: '7d' });
res.status(201).json({ customer: omit(customer, ['passwordHash']), token });
}
// POST /api/customers/login
export async function login(req: Request, res: Response) {
const { email, password } = req.body;
const customer = await db.customers.findByEmail(email.toLowerCase());
// Use the same error for both "not found" and "wrong password" to prevent email enumeration
if (!customer || !customer.passwordHash) return res.status(401).json({ error: 'Invalid email or password' });
if (customer.status === 'disabled') return res.status(403).json({ error: 'This account has been disabled' });
const valid = await bcrypt.compare(password, customer.passwordHash);
if (!valid) return res.status(401).json({ error: 'Invalid email or password' });
const token = jwt.sign({ sub: customer.id, type: 'customer' }, process.env.JWT_SECRET!, { expiresIn: '7d' });
res.json({ customer: omit(customer, ['passwordHash']), token });
}
// Address book CRUD — GET /api/customers/me/addresses
export async function listAddresses(req: AuthRequest, res: Response) {
const addresses = await db.customerAddresses.findMany({ where: { customerId: req.customerId } });
res.json({ addresses });
}
// POST /api/customers/me/addresses
export async function addAddress(req: AuthRequest, res: Response) {
const existing = await db.customerAddresses.findMany({ where: { customerId: req.customerId } });
const input = { ...addressSchema.parse(req.body), customerId: req.customerId };
if (input.isDefault || existing.length === 0) {
await db.customerAddresses.updateMany({ where: { customerId: req.customerId }, data: { isDefault: false } });
input.isDefault = true;
}
const address = await db.customerAddresses.create({ data: input });
res.status(201).json({ address });
}
// GET /api/customers/me/orders — paginated order history with tracking
export async function listOrders(req: AuthRequest, res: Response) {
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(parseInt(req.query.limit as string) || 10, 50);
const [orders, total] = await Promise.all([
db.orders.findMany({ where: { customerId: req.customerId }, skip: (page - 1) * limit, take: limit, orderBy: { createdAt: 'desc' }, include: { lineItems: true, shipments: true } }),
db.orders.count({ where: { customerId: req.customerId } }),
]);
res.json({ orders, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) } });
}---
Step 3: Configure post-checkout account creation
The highest-converting moment to ask for account creation is immediately after a successful first purchase, not before.
Shopify: The order confirmation page includes a "Create account" button automatically when customer accounts are enabled but optional. Customize the message in Online Store → Themes → Customize → Order status page.
WooCommerce: Customize the "Thank you" page message in WooCommerce → Settings → Accounts or use the Checkout Field Editor plugin to add a post-checkout account creation prompt.
What to say: "Save your details for faster checkout next time" is more compelling than "Create an account" — focus on the benefit, not the action.
Best Practices
- Always make accounts optional — never force registration before purchase; post-purchase conversion rates are much higher than pre-purchase
- Offer social login where possible (Google, Facebook) — reduces friction significantly for mobile users; use apps like Single Sign-On (SSO) for Shopify or WooCommerce Social Login for WooCommerce
- Auto-populate checkout from saved addresses — the primary value of accounts is faster checkout; ensure the default address pre-fills automatically
- Send a re-engagement email 24 hours after checkout to guest buyers inviting them to create an account — timing matters
- Enable GDPR/CCPA account deletion — every platform supports this; make sure the "delete my account" option is easy to find; see your platform's documentation for the data erasure workflow
Common Pitfalls
| Problem | Solution |
|---|---|
| Customers can't see past orders after creating an account | On Shopify, past orders from guest checkout are linked when the customer registers with the same email; verify the account linking is enabled in Settings → Customer accounts |
| Address validation fails for international customers | Don't mark state/province as required — many countries don't have them; WooCommerce handles this with country-dependent field visibility |
| JWT tokens too long-lived | Use 15-minute access tokens with refresh tokens for better security, or use server-side sessions with a secure, HttpOnly cookie |
| No way to merge duplicate customer records | Shopify and WooCommerce both support customer merge in the admin; for custom builds, build a merge tool that consolidates orders and addresses |
Related Skills
- @customer-segmentation
- @customer-lifetime-value
- @personalization-engine
{
"context": "Tests whether the agent implements the customer address book with correct schema design (country as ISO alpha-2, optional state, is_default/label fields, indexed customer_id) and correct default-address management logic (auto-default for first address, clear-then-set on new default, promote-on-delete).",
"type": "weighted_checklist",
"checklist": [
{
"name": "country as VARCHAR(2)",
"max_score": 8,
"description": "The customer_addresses table defines country as VARCHAR(2) or CHAR(2) — the two-character ISO 3166-1 alpha-2 format"
},
{
"name": "state optional",
"max_score": 8,
"description": "The state/province column in the address schema is nullable / NOT required (no NOT NULL constraint) to support countries that don't use state/province codes"
},
{
"name": "is_default field",
"max_score": 7,
"description": "Address schema includes an is_default BOOLEAN field (defaulting to false)"
},
{
"name": "label field",
"max_score": 5,
"description": "Address schema includes a label field for user-friendly names like 'Home' or 'Work'"
},
{
"name": "customer_id index",
"max_score": 7,
"description": "A database index is created on the customer_id (or customer_id foreign key) column of the addresses table"
},
{
"name": "First address auto-default",
"max_score": 10,
"description": "When adding an address and no other addresses exist for that customer, the new address is automatically set as the default"
},
{
"name": "Clear defaults before setting new default",
"max_score": 10,
"description": "When adding or updating an address as default, existing default addresses are cleared first before setting the new default (not just overwritten)"
},
{
"name": "Ownership check on update/delete",
"max_score": 10,
"description": "Before updating or deleting an address, the code verifies the address belongs to the requesting customer — returns 404 if not found or owned by another customer"
},
{
"name": "Promote on default delete",
"max_score": 10,
"description": "When the default address is deleted, the first remaining address is automatically promoted to be the new default"
},
{
"name": "UUID primary key",
"max_score": 8,
"description": "Address primary key uses UUID type (not integer/serial)"
},
{
"name": "ON DELETE CASCADE",
"max_score": 7,
"description": "The foreign key from customer_addresses to customers includes ON DELETE CASCADE"
},
{
"name": "Delete returns 204",
"max_score": 5,
"description": "The delete address endpoint returns HTTP 204 (no content) on success"
},
{
"name": "Create returns 201",
"max_score": 5,
"description": "The add address endpoint returns HTTP 201 (created) on success"
}
]
}
Customer Address Book
Problem/Feature Description
A global e-commerce platform is adding a saved-address feature to their customer accounts. Customers frequently abandon checkout because they have to re-enter their shipping details every time — the business wants to let customers save multiple addresses and have their preferred one pre-fill the checkout form automatically.
The platform has customers in the US, UK, Australia, Japan, and several other countries where postal address formats differ significantly. The engineering team has been burned before by an address system that rejected valid addresses from customers in countries like Hong Kong (no state/province) and broke the checkout for those users. The solution must handle international addresses gracefully.
Your task is to implement the address book feature: the SQL schema for storing addresses and the Express/TypeScript API endpoints for listing, adding, updating, and deleting customer addresses. The system must intelligently manage which address is the "default" so that checkout can always pre-fill from a single well-defined source of truth.
Output Specification
Produce two files:
1. schema.sql — the SQL DDL for the customer addresses table (you may include a stub customers table as context if needed) 2. addresses.ts — TypeScript Express handler functions for:
GET /api/customers/me/addressesPOST /api/customers/me/addressesPUT /api/customers/me/addresses/:idDELETE /api/customers/me/addresses/:id
You may stub database calls with placeholder functions. Focus on the logic for default-address management and request validation. Include type definitions for the address shape.
{
"context": "Tests whether the agent implements the password reset flow with correct token generation (randomBytes + SHA-256 hash stored), 1-hour expiry, email enumeration prevention, and full session/token invalidation after a successful reset.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Email enumeration prevention",
"max_score": 12,
"description": "The forgot-password endpoint returns a success-like message even when no account exists for the given email — does NOT reveal whether an account exists (e.g., always returns 'If an account exists, a reset link has been sent' or equivalent)"
},
{
"name": "crypto.randomBytes token",
"max_score": 10,
"description": "Uses Node.js built-in 'crypto' module's randomBytes(32) to generate the password reset token (not Math.random, uuid, or other methods)"
},
{
"name": "Token as hex string",
"max_score": 7,
"description": "The raw reset token is converted to a hex string with .toString('hex')"
},
{
"name": "SHA-256 hash stored",
"max_score": 12,
"description": "Only the SHA-256 hash of the token (crypto.createHash('sha256')...) is stored in the database — the raw token is NOT persisted"
},
{
"name": "1-hour token expiry",
"max_score": 8,
"description": "The reset token expiry is set to 1 hour from creation (Date.now() + 60 * 60 * 1000 or equivalent 3600 second offset)"
},
{
"name": "Expired token check",
"max_score": 8,
"description": "The reset-password endpoint validates that the token has not expired before allowing a password change"
},
{
"name": "Invalidate reset token after use",
"max_score": 10,
"description": "After a successful password reset, the used password reset record/token is deleted or invalidated so it cannot be reused"
},
{
"name": "Invalidate all sessions",
"max_score": 12,
"description": "After a successful password reset, all existing sessions for that customer are invalidated/deleted (not just the current one)"
},
{
"name": "bcrypt for new password",
"max_score": 10,
"description": "The new password is hashed with bcrypt at cost factor 12 before being stored"
},
{
"name": "Hash lookup (not raw)",
"max_score": 11,
"description": "When verifying the reset token, the submitted token is hashed first and then compared against the stored hash — does NOT compare the raw token directly against stored values"
}
]
}
Password Reset Flow
Problem/Feature Description
An online marketplace has received complaints from customers who are locked out of their accounts. The engineering team needs to build a self-service password reset flow. Security is critical here — a competitor recently suffered an account-takeover attack because their reset tokens were predictable, and another service leaked user account existence when customers entered emails to request resets.
The team wants a robust implementation: when a customer requests a reset, a time-limited link is emailed to them; when they follow the link, they can set a new password. The reset mechanism needs to be safe against token prediction, token theft (e.g., if the database is breached), and account takeover through stale reset links or old sessions.
The codebase already uses bcrypt for password hashing during registration. The reset flow should be consistent with that approach.
Output Specification
Produce a TypeScript file named password-reset.ts containing:
- The
POST /api/customers/forgot-passwordhandler - The
POST /api/customers/reset-passwordhandler - Any helper functions and type definitions needed
You may stub out database operations and the email-sending function with placeholder functions. Focus on the security logic: token generation, storage strategy, expiry, and the actions taken after a successful reset.
Also produce a short security-notes.md explaining the key design decisions made in the implementation (3-5 bullet points).
{
"context": "Tests whether the agent implements customer registration and login with the correct security patterns: bcrypt cost factor, zod validation constraints, email enumeration prevention, session token generation, and proper data sanitization before returning customer records.",
"type": "weighted_checklist",
"checklist": [
{
"name": "bcrypt import",
"max_score": 7,
"description": "Uses the 'bcrypt' package (not 'bcryptjs' or other alternatives) for password hashing"
},
{
"name": "bcrypt cost factor",
"max_score": 10,
"description": "Calls bcrypt.hash with cost factor 12 (e.g., bcrypt.hash(password, 12))"
},
{
"name": "zod validation",
"max_score": 7,
"description": "Uses zod (import { z } from 'zod') for input validation on the registration endpoint"
},
{
"name": "Password length constraints",
"max_score": 8,
"description": "Validates password with minimum 8 and maximum 128 characters (z.string().min(8).max(128) or equivalent)"
},
{
"name": "Name length constraints",
"max_score": 5,
"description": "Validates firstName and lastName with minimum 1 and maximum 100 characters"
},
{
"name": "Duplicate email 409",
"max_score": 8,
"description": "Returns HTTP 409 status when registration detects an existing account with the same email"
},
{
"name": "Email enumeration prevention (login)",
"max_score": 10,
"description": "Returns the same error message for both 'user not found' and 'wrong password' cases during login — does NOT use distinct messages that reveal whether the email exists"
},
{
"name": "Disabled account 403",
"max_score": 7,
"description": "Returns HTTP 403 (not 401 or 404) when a disabled customer attempts to log in"
},
{
"name": "sanitizeCustomer strips passwordHash",
"max_score": 10,
"description": "Removes the passwordHash field before returning customer data to the client — the response object does NOT include passwordHash"
},
{
"name": "JWT session token",
"max_score": 8,
"description": "Uses jsonwebtoken (jwt.sign) to create a session token returned alongside the customer object"
},
{
"name": "JWT payload type field",
"max_score": 8,
"description": "JWT payload includes a 'type' field set to 'customer' (e.g., { sub: customerId, type: 'customer' })"
},
{
"name": "JWT 7-day expiry",
"max_score": 7,
"description": "JWT token is created with expiresIn: '7d'"
},
{
"name": "Verification email on registration",
"max_score": 5,
"description": "Code includes a call to send a verification email after creating the customer record (e.g., sendVerificationEmail or equivalent)"
}
]
}
Customer Authentication API
Problem/Feature Description
A mid-size online retailer is building a new backend service for their storefront. The engineering team needs to implement the customer-facing authentication layer: a registration endpoint and a login endpoint. Security is a top concern — a previous vendor had a breach because they stored passwords insecurely and their login error messages made it easy for attackers to enumerate which email addresses had accounts.
The team has chosen TypeScript with Express for the API. They need you to implement the two endpoints (POST /api/customers/register and POST /api/customers/login) with proper input validation, secure password handling, session token issuance, and defensive error handling. The implementation should be production-quality — not a prototype.
Output Specification
Produce a single TypeScript file named auth.ts containing:
- The register handler function
- The login handler function
- The session creation helper
- Any necessary type definitions, imports, and schemas
You may stub out external dependencies (database calls, email sending) with placeholder functions or comments — the goal is the core logic and security decisions. Do not require a running database or mail server.
Also produce a short notes.md file explaining the key security decisions made in the implementation (2-4 bullet points).
{
"name": "finsi/customer-accounts",
"version": "0.1.0",
"summary": "Registration, profile management, address book, and order history",
"skills": {
"customer-accounts": {
"path": "SKILL.md"
}
}
}