How to Build a Marketplace App with AI
A complete tutorial on building a two-sided marketplace app with AI - buyer/seller flows, product listings, search and filters, cart, and profiles. Real prompts and generated architecture.
What We're Building
We're building a two-sided marketplace app - think a local Etsy or Depop - where users can browse and buy products, and also list their own items for sale. By the end of this tutorial, you'll have a working React Native marketplace running on your phone with:
- Browse and search products with filters
- Product detail pages with images, descriptions, and seller info
- Seller profiles with active listings
- A shopping cart with quantity management
- User profiles that switch between buyer and seller mode
This is built with React Native and Expo, generated entirely by an AI agent. I'll walk you through the exact prompt, the architecture decisions the agent made, the code patterns worth understanding, and the iterations that turned the first draft into something polished.
If you've followed the booking app tutorial, you'll recognize the workflow. Marketplaces are a step up in complexity - more data relationships, more user flows, more state to manage - but the process is the same: one detailed prompt, then targeted iterations.
Why Marketplaces Are Hard
A marketplace is one of the most architecturally demanding app types you can build. Here's what makes it complex:
Two distinct user types. Every user is potentially both a buyer and a seller. The UI needs to accommodate both roles without feeling cluttered. Navigation, profile screens, and notification logic all need to account for this duality. Product catalog with rich data. Each listing needs a title, description, price, images, category, condition, seller reference, and timestamps. The data model is deeper than most app types. Search and filtering. Users expect to search by keyword, filter by category, sort by price or date, and get results instantly. This means the data layer needs to support efficient querying on the client side. Cart logic. Add to cart, remove from cart, update quantities, calculate totals, handle items from multiple sellers. Cart state needs to persist across app restarts. Messaging between buyers and sellers. Users need to ask questions about listings before buying. This introduces a real-time communication layer that most other app types don't need. Ratings and reviews. Trust is everything in a marketplace. Buyers need to see seller ratings, and sellers need to build reputation. This creates a feedback loop that's both a data modeling challenge and a UX challenge.For a single-pass code generator, this complexity usually results in a tangled mess. An AI agent handles it by planning the architecture before writing any code, then generating files in dependency order.
The Prompt
Here's the exact prompt I used:
Build a React Native marketplace app using Expo and TypeScript called "LocalMart". It's a local buy/sell marketplace similar to Depop or Facebook Marketplace. The app should have five tabs: Home (featured listings + search bar + category filters), Explore (grid view of all products with sort/filter options), Cart (items added to cart with quantity controls and total), Sell (form to create a new listing with title, description, price, category, condition, and photos placeholder), and Profile (user info, toggle between "My Purchases" and "My Listings" views, seller rating display). Include 6 product categories: Electronics, Clothing, Home & Garden, Sports, Books, and Other. Generate 12 realistic product listings with varied categories, prices ($5-$200), conditions (New, Like New, Good, Fair), seller names, and descriptions. Use a clean modern design with white background (#ffffff), dark text (#111827), and teal accent (#0d9488 for primary actions, #ccfbf1 for subtle highlights). Products should display in a two-column grid with image placeholders, price badges, and condition tags. The cart should use AsyncStorage for persistence. Include a search function that filters by title and description, and category filter chips on the Home screen.
Let me break down the key decisions:
Named the app and defined the marketplace type. "LocalMart" and "local buy/sell marketplace" tell the agent this is peer-to-peer, not B2C. That distinction matters - it means every user can be both buyer and seller, which shapes the entire profile and navigation architecture. Five tabs with specific responsibilities. Separating Home (discovery) from Explore (browsing) gives the agent room to create two distinct experiences - curated featured items versus a full catalog with sorting. Many marketplace prompts just say "show products" and end up with a single undifferentiated list. Specified the Sell tab as a form. Without this, the agent might generate a button that opens a modal, or skip the listing creation flow entirely. Spelling out the fields (title, description, price, category, condition) gives the agent the exact data shape for the listing form and the product data model simultaneously. 12 realistic listings across 6 categories. This is critical. 12 items with varied categories means the search and filter features have enough data to be meaningful during the demo. If you only generate 3-4 items, category filters feel pointless and search returns results for everything. Two-column grid with specific visual elements. "Image placeholders, price badges, and condition tags" defines the product card component in enough detail that the agent generates a polished card layout rather than a plain text list. Category filter chips on the Home screen. This single instruction triggers the agent to build a horizontal scrollable chip bar, a filtering mechanism that connects chips to the product list, and category-based state management. Without it, the Home screen would just be a static list.What the Agent Generated
The agent produced 24 files. Here's the complete structure:
``
localmart/
├── package.json
├── app.json
├── tsconfig.json
├── babel.config.js
├── app/
│ ├── _layout.tsx # Root layout with tab navigator
│ ├── (tabs)/
│ │ ├── _layout.tsx # Tab bar config (5 tabs)
│ │ ├── index.tsx # Home - featured + search + filters
│ │ ├── explore.tsx # Explore - full catalog grid
│ │ ├── cart.tsx # Cart with quantity controls
│ │ ├── sell.tsx # Create listing form
│ │ └── profile.tsx # Buyer/seller profile
├── components/
│ ├── ProductCard.tsx # Two-column grid card
│ ├── ProductDetail.tsx # Full product view modal
│ ├── CategoryChips.tsx # Horizontal filter bar
│ ├── SearchBar.tsx # Search input with icon
│ ├── CartItem.tsx # Cart row with quantity controls
│ ├── SellerBadge.tsx # Seller name + rating display
│ └── EmptyState.tsx # Shared empty state
├── hooks/
│ ├── useProducts.ts # Product data + search/filter
│ ├── useCart.ts # Cart CRUD + AsyncStorage
│ └── useListings.ts # User's own listings management
├── types/
│ └── marketplace.ts # All TypeScript interfaces
├── constants/
│ ├── theme.ts # Colors, spacing, typography
│ └── products.ts # 12 mock product listings
└── utils/
├── searchUtils.ts # Search + filter logic
└── formatUtils.ts # Price formatting, date display
`
24 files with clean separation. The architecture decisions worth noting:
Separate Home and Explore screens. The Home screen shows featured items (a curated subset) with a search bar and category chips. The Explore screen shows the full catalog with sort options. This mirrors how real marketplaces work - Etsy's homepage is curated, the search results page is comprehensive. useProducts.ts as the single source of truth. All product data flows through this hook, which handles filtering by category, searching by keyword, and sorting. Both the Home and Explore screens consume this hook with different parameters rather than implementing their own filter logic.
useCart.ts with AsyncStorage persistence. The cart hook manages add, remove, quantity update, and total calculation, persisting to AsyncStorage on every change. This means the cart survives app restarts - a detail that users notice immediately when it's missing.
ProductDetail.tsx as a modal component. Instead of navigating to a new screen for product details (which would require a stack navigator on top of tabs), the agent created a modal overlay. This keeps the tab navigation intact and feels more native.
Key Architecture Patterns
The Product Data Model
`typescript
// types/marketplace.ts
export interface Product {
id: string;
title: string;
description: string;
price: number;
category: ProductCategory;
condition: ProductCondition;
sellerId: string;
sellerName: string;
sellerRating: number;
imageUrl: string;
createdAt: string;
isFeatured: boolean;
}
export type ProductCategory =
| "Electronics"
| "Clothing"
| "Home & Garden"
| "Sports"
| "Books"
| "Other";
export type ProductCondition = "New" | "Like New" | "Good" | "Fair";
`
The isFeatured boolean is a smart addition by the agent. It lets the Home screen filter for featured items without a separate data source. The sellerName and sellerRating are denormalized onto the product - in a real app these would come from a user lookup, but for a local-first prototype this avoids unnecessary data joining.
The Dual-Role User System
`typescript
// types/marketplace.ts
export interface User {
id: string;
name: string;
email: string;
avatar: string;
rating: number;
totalSales: number;
totalPurchases: number;
joinedDate: string;
}
export type ProfileView = "purchases" | "listings";
`
The ProfileView type drives a toggle on the profile screen. Instead of separate buyer and seller profile pages, the agent implemented a single profile screen with a segmented control that switches between "My Purchases" and "My Listings." This is the correct UX pattern - it's how Depop, Mercari, and Poshmark all handle it.
Search and Filter Implementation
`typescript
// hooks/useProducts.ts
export function useProducts() {
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] =
useState
const [sortBy, setSortBy] = useState
const filteredProducts = useMemo(() => {
let results = [...allProducts];
// Text search across title and description
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
results = results.filter(
(p) =>
p.title.toLowerCase().includes(query) ||
p.description.toLowerCase().includes(query)
);
}
// Category filter
if (selectedCategory) {
results = results.filter((p) => p.category === selectedCategory);
}
// Sort
switch (sortBy) {
case "price-low":
results.sort((a, b) => a.price - b.price);
break;
case "price-high":
results.sort((a, b) => b.price - a.price);
break;
case "newest":
results.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
break;
}
return results;
}, [searchQuery, selectedCategory, sortBy]);
return {
products: filteredProducts,
searchQuery,
setSearchQuery,
selectedCategory,
setSelectedCategory,
sortBy,
setSortBy,
};
}
`
The chained filter pattern (search, then category, then sort) inside a single useMemo is clean and efficient. The dependency array ensures recalculation only when inputs change. For 12 products this is instant. For 1000+ products you'd want to debounce the search input, but the architecture supports that change without restructuring.
Cart State Management
`typescript
// hooks/useCart.ts
export function useCart() {
const [items, setItems] = useState
// Load from AsyncStorage on mount
useEffect(() => {
AsyncStorage.getItem("cart").then((data) => {
if (data) setItems(JSON.parse(data));
});
}, []);
// Persist on every change
useEffect(() => {
AsyncStorage.setItem("cart", JSON.stringify(items));
}, [items]);
function addToCart(product: Product) {
setItems((prev) => {
const existing = prev.find((i) => i.product.id === product.id);
if (existing) {
return prev.map((i) =>
i.product.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
);
}
return [...prev, { product, quantity: 1 }];
});
}
function removeFromCart(productId: string) {
setItems((prev) => prev.filter((i) => i.product.id !== productId));
}
function updateQuantity(productId: string, quantity: number) {
if (quantity <= 0) {
removeFromCart(productId);
return;
}
setItems((prev) =>
prev.map((i) =>
i.product.id === productId ? { ...i, quantity } : i
)
);
}
const total = items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0
);
return { items, addToCart, removeFromCart, updateQuantity, total };
}
`
The addToCart function handles the "item already in cart" case by incrementing quantity instead of adding a duplicate. The updateQuantity function handles zero-or-below by removing the item entirely. These are the edge cases that separate a usable cart from a buggy one, and the agent caught both.
The Code Worth Understanding
The Product Card Component
The product card is the most-rendered component in the app, appearing in both the Home and Explore grids:
`typescript
// components/ProductCard.tsx (simplified)
export function ProductCard({ product, onPress }: ProductCardProps) {
return (
{getCategoryEmoji(product.category)}
{product.title}
\${product.price}
style={[ styles.conditionBadge, getConditionStyle(product.condition), ]} >
);
}
`
The getCategoryEmoji function is a practical solution to the "no real images" problem in a prototype. Instead of broken image placeholders, each category gets a relevant emoji on a colored background. Electronics get a laptop emoji, Clothing gets a shirt, and so on. It's visually clear and requires zero asset management.
The Category Filter Chips
`typescript
// components/CategoryChips.tsx
export function CategoryChips({ selected, onSelect }: CategoryChipsProps) {
const categories: ProductCategory[] = [
"Electronics", "Clothing", "Home & Garden",
"Sports", "Books", "Other",
];
return (
{categories.map((cat) => (
key={cat} style={[styles.chip, selected === cat && styles.chipActive]} onPress={() => onSelect(selected === cat ? null : cat)} > style={[ styles.chipText, selected === cat && styles.chipTextActive, ]} > {cat}
))}
);
}
`
The toggle behavior (selected === cat ? null : cat) means tapping an active category deselects it, returning to the unfiltered view. This is the expected behavior that users assume but that many implementations miss - without it, once you select a category, there's no way back to "show all" without a separate "Clear filters" button.
Iterating the Marketplace
The first generation was architecturally sound but needed UX polish. Here are three targeted iterations:
Iteration 1 - Favorites/wishlist: "Add a favorites/wishlist feature. Each product card should have a small heart icon in the top-right corner. Tapping it toggles the favorite state. Store favorites in AsyncStorage. Add a 'Favorites' section to the profile screen that shows all favorited products in a grid. Use a filled teal heart for favorited items and an outlined gray heart for unfavorited."This added a useFavorites.ts hook and a heart icon overlay on every ProductCard. The agent correctly placed the favorites list on the Profile screen rather than creating a new tab, keeping the navigation structure clean.
This gave the product detail modal more depth. The agent generated believable review text ("Fast shipping, item exactly as described" / "Good condition but took a week to arrive - 3 stars") that makes the demo feel real during presentations.
Iteration 3 - Category filters with horizontal scroll: "On the Explore screen, add a horizontal scrollable row of category cards at the top, above the product grid. Each category card should show the category emoji, name, and product count. They should be larger than the Home screen chips - about 80x100px with a soft teal background. Tapping a category card filters the grid below."This differentiated the Explore screen from the Home screen. The Home screen has small filter chips for quick toggling. The Explore screen has visual category cards that double as navigation and as a count of available items per category.
Marketplace App Patterns That Scale
The generated code is a fully functional local prototype. Here's what would change if you were taking this to production:
API integration points. The useProducts hook currently reads from a local constants file. In production, it would fetch from an API endpoint. The hook's interface (returns filtered products, accepts search/filter/sort params) stays identical - you're swapping the data source, not the architecture.
Payment integration. The cart already calculates totals correctly. Adding Stripe requires a checkout screen that collects shipping info, a server-side endpoint to create a PaymentIntent, and the Stripe React Native SDK for the payment sheet. The cart data structure already has everything Stripe needs (line items with prices and quantities).
Push notifications. Buyers need "your order shipped" notifications. Sellers need "new order" and "someone favorited your item" notifications. Expo's push notification service handles the device token management. The notification types map directly to the events already happening in the app (cart checkout, favorite toggle).
Real-time messaging. Buyer-seller messaging is the biggest architectural addition. You'd add Supabase Realtime or a dedicated chat service (Stream, SendBird). The ProductDetail modal already shows the seller - adding a "Message Seller" button that opens a chat screen is a UI change, not an architecture rewrite.
Image uploads. The Sell form currently has a photos placeholder. Expo's ImagePicker library handles camera/gallery selection, and Supabase Storage handles upload and CDN delivery. The product data model already has an imageUrl` field waiting for real URLs.
The point of generating with an AI agent isn't to ship the generated code directly to the App Store. It's to get a working architecture with correct data models, clean component boundaries, and functional business logic in minutes instead of weeks. From there, a developer can add real infrastructure incrementally, knowing the foundation is solid.
For a different take on the generation workflow, the habit tracker tutorial covers a simpler app type that's great for learning the basics. And if you're evaluating whether an AI agent builder is the right tool for your project, the comparison with no-code platforms breaks down the tradeoffs honestly.
Ready to build your app?
Start Building for FreeKeep reading
How to Build a Restaurant Website with AI
A step-by-step tutorial for building a professional restaurant website with AI - from menu pages to reservation forms. Exact prompts, generated code, and iteration tips.
ReadtutorialHow to Build a Booking App with AI Using React Native
A step-by-step tutorial for building a complete booking and appointment app with an AI agent - exact prompts, generated architecture, iteration strategy, and the specific patterns that make booking flows work in React Native.
ReadtutorialHow to Build a React Native App with AI: A Habit Tracker Walkthrough
A real walkthrough of building a habit tracker app with an AI agent - exact prompts, generated file structure, Expo commands, and the mistakes to avoid.
Read