# Product

> E-commerce product with pricing, inventory, dimensions, and create/update variants.

- Name: `product`
- Categories: ecommerce
- Detail page: https://open-types.dev/types/product

## Install

```bash
# Types only
npx shadcn add @open-types/product

# Types + Zod v4 validators
npx shadcn add @open-types/product-zod

# Types + real-world examples
npx shadcn add @open-types/product-examples
```

## Types

```typescript
export type CurrencyCode = string;

export type DimensionsUnit = "cm" | "in" | "m" | "ft";

export interface Dimensions {
  length: number;
  width: number;
  height: number;
  unit?: DimensionsUnit;
}

export interface Product {
  id: string;
  name: string;
  description?: string;
  price: number;
  compareAtPrice?: number;
  currency?: CurrencyCode;
  sku: string;
  stock: number;
  categories?: string[];
  tags?: string[];
  isActive?: boolean;
  weight?: number;
  dimensions?: Dimensions;
}

export type ProductCreate = Omit<Product, "id">;

export type ProductUpdate = Partial<ProductCreate>;
```

## Zod validator

```typescript
import * as z from "zod";
import type {
  CurrencyCode,
  Dimensions,
  Product,
  ProductCreate,
  ProductUpdate,
} from "../types/product";

const ISO_CURRENCY_REGEX = /^[A-Z]{3}$/;

export const currencyCodeSchema = z
  .string()
  .regex(ISO_CURRENCY_REGEX, { error: "Currency must be a 3-letter ISO code (e.g. USD)" })
  .default("USD");

export const dimensionsSchema = z.object({
  length: z.number().positive({ error: "Length must be positive" }),
  width: z.number().positive({ error: "Width must be positive" }),
  height: z.number().positive({ error: "Height must be positive" }),
  unit: z.enum(["cm", "in", "m", "ft"]).default("cm"),
}) satisfies z.ZodType<Dimensions>;

export const productSchema = z.object({
  id: z.uuid({ error: "Invalid product ID" }),
  name: z.string().min(1, { error: "Product name is required" }),
  description: z.string().optional(),
  price: z
    .number()
    .positive({ error: "Price must be positive" })
    .multipleOf(0.01, { error: "Price must have at most 2 decimal places" }),
  compareAtPrice: z
    .number()
    .positive({ error: "Compare-at price must be positive" })
    .multipleOf(0.01)
    .optional(),
  currency: currencyCodeSchema,
  sku: z.string().min(1, { error: "SKU is required" }),
  stock: z
    .number()
    .int({ error: "Stock must be a whole number" })
    .min(0, { error: "Stock cannot be negative" }),
  categories: z.array(z.string()).default([]),
  tags: z.array(z.string()).default([]),
  isActive: z.boolean().default(true),
  weight: z.number().positive({ error: "Weight must be positive" }).optional(),
  dimensions: dimensionsSchema.optional(),
}) satisfies z.ZodType<Product>;

export const productCreateSchema = productSchema.omit({ id: true });

export const productUpdateSchema = productSchema.omit({ id: true }).partial();

export type {
  CurrencyCode,
  DimensionsUnit,
  Dimensions,
  Product,
  ProductCreate,
  ProductUpdate,
} from "../types/product";
```

## Examples

```typescript
// Source: hand-crafted
import type {
  CurrencyCode,
  DimensionsUnit,
  Dimensions,
  Product,
  ProductCreate,
  ProductUpdate,
} from "../types/product";

export const currencyCodeExamples = {
  usd: "USD",
  eur: "EUR",
  jpy: "JPY",
  gbp: "GBP",
} as const satisfies Record<string, CurrencyCode>;

export const dimensionsExamples = {
  cm: { length: 30, width: 20, height: 5, unit: "cm" } satisfies Dimensions,
  inches: { length: 12, width: 8, height: 2, unit: "in" } satisfies Dimensions,
  noUnit: { length: 1, width: 1, height: 1 } satisfies Dimensions,
  metric: { length: 1, width: 0.5, height: 0.3, unit: "m" } satisfies Dimensions,
} as const;

const widget = {
  id: "11111111-2222-4333-8444-555555555551",
  name: "Premium Widget",
  description: "A high-quality widget engineered for daily use.",
  price: 29.99,
  compareAtPrice: 39.99,
  currency: "USD",
  sku: "WIDGET-PREMIUM-001",
  stock: 142,
  categories: ["widgets", "premium"],
  tags: ["new", "featured", "best-seller"],
  isActive: true,
  weight: 0.5,
  dimensions: dimensionsExamples.cm,
} as const satisfies Product;

export const productExamples = {
  full: widget,
  minimal: {
    id: "22222222-2222-4333-8444-555555555552",
    name: "Basic Item",
    price: 9.99,
    sku: "ITEM-BASIC-002",
    stock: 0,
  } satisfies Product,
  outOfStock: { ...widget, id: "33333333-3333-4333-8444-555555555553", stock: 0, isActive: false } satisfies Product,
  digital: {
    id: "44444444-4444-4333-8444-555555555554",
    name: "E-Book Bundle",
    price: 19.99,
    currency: "USD",
    sku: "EBOOK-BUNDLE-001",
    stock: 9999,
    tags: ["digital", "download"],
    isActive: true,
  } satisfies Product,
} as const;

export const dimensionUnitValues: readonly DimensionsUnit[] = ["cm", "in", "m", "ft"];

export const productCreateExamples = {
  newWidget: {
    name: "New Widget",
    price: 24.99,
    currency: "USD",
    sku: "WIDGET-NEW-001",
    stock: 100,
    isActive: true,
  } satisfies ProductCreate,
} as const;

export const productUpdateExamples = {
  priceOnly: { price: 24.99 } satisfies ProductUpdate,
  restock: { stock: 200, isActive: true } satisfies ProductUpdate,
  empty: {} satisfies ProductUpdate,
} as const;
```
