Skip to main content
RapidDev - Software Development Agency
Lovable PromptsMarketplaceAdvanced

Build an Auction Platform in Lovable

A timed-auction marketplace with live bid feeds via Supabase Realtime, server-side atomic bid validation in a Postgres RPC with row locks, soft-close auto-extend, and winner-only Stripe charge on close.

Time to MVP

a few days

Credits

~200-350 credits for full chain

Difficulty

Advanced

Cloud features

4

TL;DR

The one-paragraph version before you dive in.

Paste the starter prompt below into Lovable Build mode and get a timed-auction marketplace scaffold with live bid feeds, a server-side bid validation RPC using SELECT FOR UPDATE to prevent lost-update bugs, and a Stripe charge-on-close flow. Full build takes a few days and around 200-350 credits on a Pro plan with top-ups.

Setup checklist

Complete these steps in Lovable before pasting the starter prompt — takes about 5 minutes.

Cloud tab settings

Database

Stores auctions, bids, profiles, and payments. Realtime must be enabled on auctions and bids tables for the live bid feed.

  1. 1Click the + button next to Preview in the top toolbar to open the Cloud tab
  2. 2Click Database — the starter prompt will create all tables and the place_bid RPC via migration
  3. 3After the starter prompt runs, go to Cloud tab → Database → Replication and toggle Realtime ON for the auctions and bids tables

Auth

Email+password for bidders. Admin/seller role stored in app_metadata for moderator queue gating.

  1. 1Cloud tab → Users & Auth
  2. 2Enable Email sign-in (default — already on)
  3. 3To make a user a seller or admin: click their row in the Users list → Edit → set app_metadata to {"role": "admin"}

Storage

Public bucket for auction item photos uploaded during listing creation.

  1. 1Cloud tab → Storage
  2. 2Click Create bucket, name it auction-images, toggle Public ON (item photos are publicly visible)
  3. 3Note the bucket name — the starter prompt references it for the image upload flow in /sell

Edge Functions

Four functions handle bid validation, auction closure scheduling, Stripe charge-on-close, and soft-close extension.

  1. 1Cloud tab → Edge Functions — leave empty for now; the follow-up prompts will generate each function
  2. 2For the close-auction function: after it's generated, click Schedule and set it to run every 60 seconds
  3. 3Add STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET to Cloud tab → Secrets before running the charge-winner follow-up

Secrets (Cloud tab → Secrets)

STRIPE_SECRET_KEY

Purpose: Allows charge-winner Edge Function to create Stripe PaymentIntents for auction winners.

Where to get it: https://dashboard.stripe.com/apikeys — copy the Secret key (sk_test_... for testing, sk_live_... for production). In Lovable, use the API Key icon in the chat input or paste directly in Cloud tab → Secrets.

STRIPE_WEBHOOK_SECRET

Purpose: Verifies that incoming webhook events actually came from Stripe, not a third party.

Where to get it: https://dashboard.stripe.com/webhooks — after creating the webhook endpoint pointing to your deployed app, click the endpoint row and copy the Signing secret (whsec_...).

RESEND_API_KEY

Purpose: Optional — sends winner payment-link emails when a Stripe customer ID is missing.

Where to get it: https://resend.com/api-keys — create a key with Send access.

Preflight checklist

  • You're in a fresh Lovable project (Vite + React + TypeScript + Tailwind + shadcn/ui already scaffolded — that's the default)
  • You're on Pro $25/mo plan and budget 1-2 top-ups (~$15 per 50 credits) — bid-validation RPC and Stripe wiring are the most credit-intensive parts of this build
  • You understand this is an advanced build — the bid contention, Realtime subscription, and Stripe charge-on-close chain have more moving parts than a typical CRUD app
  • Credit estimates for this kit are extrapolated from marketplace + chat-application + payment-gateway cousin patterns — there is no documented exact-match Lovable auction build to triangulate against, so treat all numbers as ranges

The starter prompt — paste this first

Copy this. Paste it into Lovable Agent Mode (the default chat at the bottom-left of the editor). Hit send.

lovable-agent-mode.txt
~60-100 credits
Build an auction platform marketplace. Stack assumptions: React + Vite + TypeScript + Tailwind + shadcn/ui already scaffolded by Lovable, React Router for routes, Supabase JS client for data access against Lovable Cloud.

## Database schema (create one migration file)

Create four tables in the public schema:

1. `auctions` — id (uuid pk default gen_random_uuid()), seller_id (uuid not null references auth.users(id)), title (text not null), description (text), image_path (text), start_price_cents (int not null), current_bid_cents (int not null default 0), current_bidder_id (uuid references auth.users(id)), bid_count (int not null default 0), starts_at (timestamptz not null default now()), ends_at (timestamptz not null), status (text not null default 'live' check (status in ('draft','live','closed','cancelled'))), reserve_cents (int), increment_cents (int not null default 100), soft_close_window_s (int default 60), created_at (timestamptz default now()).

2. `bids` — id (uuid pk default gen_random_uuid()), auction_id (uuid not null references auctions(id) on delete cascade), bidder_id (uuid not null references auth.users(id)), amount_cents (int not null), placed_at (timestamptz default now()), client_request_id (uuid) — idempotency key.

3. `profiles` — id (uuid pk references auth.users(id) on delete cascade), display_name (text), avatar_url (text), stripe_customer_id (text), created_at (timestamptz default now()).

4. `payments` — id (uuid pk default gen_random_uuid()), auction_id (uuid references auctions(id)), winner_id (uuid references auth.users(id)), stripe_payment_intent_id (text), amount_cents (int), status (text not null default 'pending' check (status in ('pending','succeeded','failed','refunded'))), created_at (timestamptz default now()).

Also create a view `winning_bids` as: SELECT a.id as auction_id, a.title, a.current_bidder_id, a.current_bid_cents, a.ends_at FROM auctions a WHERE a.status = 'closed'.

## RLS policies

Enable RLS on all four tables.
- auctions: SELECT for anyone where status = 'live'; INSERT for authenticated where seller_id = auth.uid(); UPDATE only via RPC (no direct update policy for users).
- bids: SELECT for anyone; no direct INSERT policy (insert only via place_bid RPC).
- profiles: SELECT for anyone (display_name only); INSERT/UPDATE for auth.uid() = id.
- payments: SELECT for authenticated where winner_id = auth.uid() OR has_role('admin'); no INSERT/UPDATE for users.

Create a SECURITY DEFINER plpgsql function `has_role(p_role text)` that returns boolean: `RETURN (auth.jwt() ->> 'app_metadata')::jsonb ->> 'role' = p_role;`

## place_bid RPC (the heart of the auction)

Create a SECURITY DEFINER plpgsql function `place_bid(p_auction_id uuid, p_amount_cents integer, p_client_request_id uuid DEFAULT NULL)` that:
1. Runs in a transaction with SELECT ... FOR UPDATE on the auctions row (to lock it).
2. Validates that status = 'live' and now() < ends_at — raises exception 'Auction is not accepting bids' if not.
3. Validates that p_amount_cents >= current_bid_cents + increment_cents — raises exception 'Bid must be at least $X.XX' if not (format the minimum nicely).
4. Rejects if auth.uid() = seller_id (anti-shilling).
5. Optionally checks for duplicate client_request_id in bids (idempotency).
6. Handles soft-close: if (ends_at - now()) < soft_close_window_s seconds, extend ends_at by soft_close_window_s.
7. INSERTs into bids (auction_id, bidder_id, amount_cents, client_request_id) and UPDATEs auctions SET current_bid_cents = p_amount_cents, current_bidder_id = auth.uid(), bid_count = bid_count + 1, ends_at = (extended if soft-close triggered) in one step.
8. Returns the new current_bid_cents and the updated ends_at.

Grant EXECUTE on place_bid to authenticated.

## Layout and pages

Create `src/layouts/PublicLayout.tsx` — top nav with logo, 'Live Auctions' link, search input, user avatar (shadcn Avatar + DropdownMenu with Sign In / My Account / Sign Out). Mobile-friendly hamburger for nav.

Create these routes in src/App.tsx:
- / (Home.tsx) — grid of live auctions, filtered by status='live', sorted by ends_at ASC. Each item = AuctionCard component.
- /auctions/:id (AuctionDetail.tsx) — shows auction image, title, description, CountdownTimer (re-renders every second from ends_at, red when <60s), HighBidBadge (shows current bid + bidder display_name; if current_bidder_id = auth.uid() shows 'You are winning'), BidInput (min-increment validation in UI as UX only), BidHistoryList (last 20 bids). All three use Supabase Realtime subscriptions on auctions:id=eq.{id} and bids:auction_id=eq.{id}.
- /sell (Sell.tsx) — create auction form: title, description, image upload to auction-images bucket, start_price, increment_cents, reserve_cents optional, ends_at datetime picker. Auth-gated.
- /account (Account.tsx) — tabs: My Bids (bids placed, with auction status), My Listings (seller's auctions), Won Items (winning_bids view where current_bidder_id = me, with Pay Now button for pending payments).
- /admin/moderation (Moderation.tsx) — admin-only (has_role('admin')), list of all auctions with status controls.
- /login (Login.tsx) — email+password form.

## Key components

Create: AuctionCard (image + title + current bid + countdown badge + status chip), CountdownTimer (accepts ends_at prop, updates every second, shows HH:MM:SS, turns text-red-500 when <60s), BidInput (shows minimum bid, calls place_bid RPC on submit, shows error from RPC clearly, shows 'Extended!' toast when returned ends_at > previous ends_at), BidHistoryList (Realtime-subscribed list of latest bids with display_name + amount + time), HighBidBadge (green if auth.uid() = current_bidder_id, otherwise shows leader's display_name).

## Styling

Dark mode as default (className='dark' on html). Tailwind dark: variants throughout. Accent color amber-500 for live bid CTAs, bid amounts, and countdown when active. Monospace font for countdown digits (font-mono). Subtle ring-2 ring-amber-400 pulse animation on BidInput when a new bid arrives via Realtime.

Generate the migration file first, then the place_bid RPC, then PublicLayout, then pages in the order listed, then components. After the migration is generated, pause and confirm before proceeding.

What this prompt generates

  • Creates one SQL migration with 4 tables, RLS policies, has_role() function, place_bid RPC with SELECT FOR UPDATE, soft-close logic, and winning_bids view
  • Scaffolds PublicLayout with responsive top nav and auth-aware user menu
  • Generates Home grid, AuctionDetail with working Realtime bid feed and countdown timer, /sell creation form, and /account dashboard
  • Builds all five core components: AuctionCard, CountdownTimer, BidInput (calling place_bid RPC), BidHistoryList, HighBidBadge
  • Wires admin moderation queue gated by has_role('admin')

Paste into: Lovable Agent Mode (the default chat at the bottom-left of the editor)

Expected output

What Lovable will generate after the starter prompt runs successfully.

Files
supabase/migrations/0001_auction_schema.sql

4 tables + RLS + has_role() + place_bid RPC + winning_bids view

src/layouts/PublicLayout.tsx

Top nav with logo, search, user avatar dropdown

src/components/AuthGuard.tsx

Redirects to /login if user not authenticated

src/components/AuctionCard.tsx

Grid card with image, title, current bid, countdown badge

src/components/CountdownTimer.tsx

Live countdown from ends_at, turns red under 60s

src/components/BidInput.tsx

Calls place_bid RPC, shows RPC errors, Extended! toast on soft-close

src/components/BidHistoryList.tsx

Realtime-subscribed list of recent bids

src/components/HighBidBadge.tsx

Green You are winning badge or shows leader display_name

src/components/ImageUploader.tsx

Drag-drop upload to auction-images Storage bucket

src/pages/Home.tsx

Live auctions grid sorted by ends_at

src/pages/AuctionDetail.tsx

Detail view with Realtime bid feed, countdown, bid input

src/pages/Sell.tsx

Auction creation form with image upload

src/pages/Account.tsx

My Bids, My Listings, Won Items tabs

src/pages/admin/Moderation.tsx

Admin-only auction moderation queue

Routes
/

Live auctions grid

/auctions/:id

Auction detail with Realtime bid feed and countdown

/sell

Create new auction — auth-gated

/account

Bidder/seller account — bids, listings, won items

/admin/moderation

Admin auction moderation queue

/login

Email+password sign-in

DB Tables
auctions
ColumnType
iduuid primary key
seller_iduuid references auth.users(id)
titletext not null
current_bid_centsint not null default 0
current_bidder_iduuid references auth.users(id)
ends_attimestamptz not null
statustext check in (draft,live,closed,cancelled)
increment_centsint not null default 100
soft_close_window_sint default 60

RLS: Public SELECT for status=live; INSERT for seller only; UPDATE only via place_bid RPC

bids
ColumnType
iduuid primary key
auction_iduuid references auctions(id) on delete cascade
bidder_iduuid references auth.users(id)
amount_centsint not null
placed_attimestamptz default now()
client_request_iduuid

RLS: Public SELECT; INSERT only via place_bid RPC — no direct client INSERT policy

profiles
ColumnType
iduuid primary key references auth.users(id)
display_nametext
avatar_urltext
stripe_customer_idtext

RLS: Public SELECT for display_name; per-user write of own row

payments
ColumnType
iduuid primary key
auction_iduuid references auctions(id)
winner_iduuid references auth.users(id)
stripe_payment_intent_idtext
amount_centsint
statustext check in (pending,succeeded,failed,refunded)

RLS: SELECT for winner_id = auth.uid() OR has_role('admin'); no user INSERT

Components
AuctionCard

Grid item with image, current bid, countdown badge, status chip

CountdownTimer

Live countdown that turns red under 60 seconds

BidInput

Validates minimum increment, calls place_bid RPC, surfaces RPC errors

BidHistoryList

Realtime-subscribed ordered list of recent bids

HighBidBadge

Shows You are winning (green) or leader display name

ImageUploader

Drag-drop upload to auction-images Storage bucket

Follow-up prompts

Paste these into Agent Mode one by one, in order, after the starter prompt finishes.

1

Enable Realtime on auctions and bids tables

Live bid feed so every spectator sees the new high bid within ~1 second without refreshing

~25-35 credits
prompt
Enable Supabase Realtime on the auctions and bids tables. In AuctionDetail.tsx, add two Realtime subscriptions inside a single useEffect:

1. Subscribe to channel 'auction-{id}' filtering on auctions:id=eq.{auctionId}. On UPDATE event, update the local auction state with the new current_bid_cents, current_bidder_id, bid_count, and ends_at.

2. Subscribe to channel 'bids-{id}' filtering on bids:auction_id=eq.{auctionId}. On INSERT event, prepend the new bid to BidHistoryList and update the HighBidBadge.

Also add a re-subscribe on document.visibilitychange so the subscriptions reconnect when the user returns to the tab after backgrounding the browser. Add a 30-second polling fallback: if no Realtime event has been received in 30s, fetch the latest 5 bids and the current auction state directly from Supabase. Clean up all subscriptions on component unmount.

When to use: Once the starter prompt finishes and you verify place_bid writes work in two browser tabs

2

Schedule auction closure with close-auction Edge Function

Server-controlled auction closure so auctions end at exactly ends_at regardless of whether any user is watching

~35-45 credits
prompt
Create a Supabase Edge Function at supabase/functions/close-auction/index.ts. This function runs on a schedule every 60 seconds.

The function should:
1. Query auctions where ends_at < now() AND status = 'live'.
2. For each, set status = 'closed' in a single UPDATE.
3. For each closed auction where current_bidder_id IS NOT NULL (there was a winner), INSERT a row into payments (auction_id, winner_id = current_bidder_id, amount_cents = current_bid_cents, status = 'pending').
4. If there was no winner (current_bidder_id IS NULL), skip the payments insert — just close the auction.
5. After writing all payments rows, invoke the charge-winner Edge Function for each payment row (pass payment_id as the body). Use the Supabase JS client with the service-role key to call the function.
6. Return a summary JSON: {closed_count, payments_created}.

This function requires the service-role key — use Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') (Lovable Cloud auto-injects this). After generating, go to Cloud tab → Edge Functions → close-auction → Schedule and set it to run every 60 seconds.

When to use: Once live bidding and Realtime work; this is required before going live to real bidders

3

Wire Stripe charge-on-close with charge-winner Edge Function

Automatic winner payment via saved Stripe card, with email fallback for bidders without a saved method

~60-80 credits
prompt
Create a Supabase Edge Function at supabase/functions/charge-winner/index.ts. This function is called by close-auction with a payment_id in the body.

The function should:
1. Read the payment row from the payments table using the provided payment_id.
2. Look up profiles.stripe_customer_id for the winner.
3. If stripe_customer_id exists: create a Stripe PaymentIntent with amount = payment.amount_cents, currency = 'usd', customer = stripe_customer_id, confirm = true (auto-confirm if they have a saved payment method). Update payments.status to 'succeeded' or 'failed' based on the result.
4. If stripe_customer_id is null: send a Resend email to the winner's email address with a Stripe Payment Link URL and a message like 'Congratulations — you won [auction title]! Complete your purchase here: [link]'. Update payments.status to 'pending' with a note.
5. Return {payment_id, status}.

Use STRIPE_SECRET_KEY from Deno.env.get() and RESEND_API_KEY for emails. Never hardcode keys. The function must handle errors gracefully — if Stripe fails, log the error, set status='failed', and return a 200 (so close-auction doesn't retry in a loop).

When to use: Once close-auction works; add STRIPE_SECRET_KEY to Cloud Secrets before running this prompt

4

Add ensure-stripe-customer trigger on profile creation

Stripe customer IDs created at signup so charge-winner always has a customer to charge

~40-50 credits
prompt
Create a Supabase Edge Function at supabase/functions/ensure-stripe-customer/index.ts that accepts a user object (id, email) and creates a Stripe Customer record if one doesn't already exist for that user.

The function should:
1. Check profiles.stripe_customer_id for the given user id — if it already exists, return it immediately.
2. If not, call stripe.customers.create({ email: user.email, metadata: { supabase_user_id: user.id } }).
3. Update profiles.stripe_customer_id = customer.id where id = user.id using the service-role key.
4. Return { stripe_customer_id }.

Then create a Postgres trigger function `on_auth_user_created` that fires AFTER INSERT ON auth.users. The trigger should call ensure-stripe-customer via an HTTP request using net.http_post() (pg_net extension). Also run a one-time backfill: for all profiles where stripe_customer_id IS NULL, call ensure-stripe-customer.

Add a backfill button in /admin/moderation that runs the backfill on demand.

When to use: Before going live to real bidders — run the backfill for existing users immediately after deploying

5

Add anti-shilling fraud checks inside place_bid

Server-side anti-shilling guards that block seller self-bidding and rate-limit bid spam

~35-45 credits
prompt
Update the place_bid Postgres RPC to add three anti-shilling guards:

1. Seller cannot bid on own auction: if auth.uid() = (SELECT seller_id FROM auctions WHERE id = p_auction_id), raise exception 'Sellers cannot bid on their own listings.'.

2. Rate limit: if EXISTS (SELECT 1 FROM bids WHERE bidder_id = auth.uid() AND auction_id = p_auction_id AND placed_at > now() - interval '2 seconds'), raise exception 'Please wait a moment before placing another bid.'

3. Flag for review: if (SELECT count(*) FROM bids b JOIN auctions a ON a.id = b.auction_id WHERE b.bidder_id = auth.uid() AND a.seller_id = (SELECT seller_id FROM auctions WHERE id = p_auction_id)) > 50, INSERT into a new table `fraud_flags` (user_id, reason, created_at) — create this table with RLS admin-only. Do not block the bid, just flag it.

Create the fraud_flags table in a new migration with RLS admin-only SELECT and INSERT via place_bid (SECURITY DEFINER bypass). Add a Fraud Flags tab in /admin/moderation.

When to use: Before going live to real bidders — these guards cannot be added retroactively without migrating existing bids

6

Add seller dashboard with reserve-not-met logic

Seller dashboard with reserve-not-met handling and relist flow for failed auctions

~35-45 credits
prompt
Add a Seller Dashboard section to /account under a My Listings tab. Show each of the seller's auctions with: title, current status, current_bid_cents vs reserve_cents, bid_count, ends_at countdown.

For closed auctions where current_bid_cents < reserve_cents: show a yellow 'Reserve not met' badge and a 'Relist' button that creates a new auction pre-filled with the same title/description/image/reserve and a new ends_at.

For closed auctions where the reserve was met but payment.status = 'pending': show 'Awaiting payment' with the winner's display_name.

For closed auctions where payment.status = 'succeeded': show 'Sold' with the final amount.

Update close-auction Edge Function: when closing an auction, if current_bid_cents < reserve_cents, do NOT create a payments row — set status='closed' and log the reserve-not-met outcome in a new column `close_reason` (text, values: 'won','reserve_not_met','no_bids','cancelled') on the auctions table. Add this column in a new migration.

When to use: Once you have repeat sellers or reserve pricing matters for your use case

7

Add winner notification emails via Resend

Branded winner and seller notification emails on auction close

~20-30 credits
prompt
Add Resend winner notification emails to the charge-winner Edge Function. After processing a payment (whether succeeded or pending), send a branded email to the winner using RESEND_API_KEY from Deno.env.get():

Subject: 'You won: [auction title]!'
Body: Congratulate the winner, show the winning amount, include a link to /account and a link to their Stripe portal (if stripe_customer_id exists).

Also send a seller notification email: 'Your auction [title] has closed. Winning bid: $X.XX. Winner: [display_name].'

Use Resend's Node.js-compatible SDK: import { Resend } from 'https://esm.sh/resend@latest'. Wrap both sends in try/catch — a failed email must never fail the webhook response.

Add RESEND_API_KEY to Cloud tab → Secrets before running this prompt.

When to use: Once charge-winner works end-to-end and you want off-brand Stripe emails replaced

Common errors

Real error strings you'll see. Find yours, paste the fix prompt.

Bid was rejected: current bid is now $510 (your bid: $501)

Working as designed — your bidder lost the race to another bid. The RPC's SELECT FOR UPDATE lock did its job and rejected the stale bid.

Fix — paste into Lovable Agent Mode
Refresh the BidInput minimum by reading the Realtime update that came in with the new current_bid_cents. Show the user a toast: 'Someone just outbid you — minimum is now $[new_minimum]' and update the input placeholder. Do not treat this as a bug.
infinite recursion detected in policy for relation "bids"

You added an RLS policy on bids that joins back to auctions, and the auctions policy in turn queries bids — creating a circular reference that Postgres detects and rejects.

Fix — paste into Lovable Agent Mode
Create a SECURITY DEFINER plpgsql function can_see_bid(p_bid_id uuid) that returns boolean and checks the auction's status directly without triggering RLS on bids. Use this function in the bids SELECT policy: USING (can_see_bid(id)). Do NOT use a SQL-language function — it gets inlined and still recurses.
permission denied for function place_bid

The RPC was created but EXECUTE was not granted to the authenticated role.

Manual fix

Open Cloud tab → Database → SQL Editor and run: GRANT EXECUTE ON FUNCTION place_bid(uuid, integer, uuid) TO authenticated;

Realtime bid feed stops updating after 30 seconds

Free tier caps concurrent Realtime connections at 200. If your auction goes viral, additional spectators silently fail to subscribe. Also, Lovable's default useEffect doesn't always re-subscribe after tab focus loss.

Fix — paste into Lovable Agent Mode
In AuctionDetail.tsx, wrap the Realtime subscription in a useEffect that also listens to the document visibilitychange event and re-subscribes when document.visibilityState becomes 'visible'. Add a 30-second polling fallback that fetches the latest 5 bids from Supabase directly if no Realtime event has arrived in that window.
No such customer: cus_xxx when calling charge-winner

The bidder never had a Stripe customer created at sign-up — the ensure-stripe-customer trigger either wasn't added yet or failed silently for that user.

Fix — paste into Lovable Agent Mode
In the charge-winner Edge Function, before trying to create a PaymentIntent, check if profiles.stripe_customer_id is null. If so, call ensure-stripe-customer inline to create the customer, save the ID, then proceed. Also run the backfill button in /admin/moderation to fix all existing users without a stripe_customer_id.
Auction closed but charge-winner never ran

The close-auction function set status='closed' but did not chain to invoke charge-winner. Edge Functions do not auto-trigger each other.

Fix — paste into Lovable Agent Mode
Inside close-auction, after setting status='closed' and inserting the payments row, add an explicit call using the Supabase JS client: await supabase.functions.invoke('charge-winner', { body: { payment_id: payment.id } }). Use the service-role key for this client so the invocation has full permissions.

Cost reality

What this build actually costs — no surprises on your card.

Recommended Lovable plan

Pro $25/mo plus 1-2 top-ups ($15 per 50 credits). The bid-validation RPC and Stripe charge-on-close wiring are the most credit-expensive parts — Stripe debugging alone has been reported to burn a month's Pro credits in an afternoon.

Monthly run cost breakdown

~200-350 credits (starter ~60-100, seven follow-ups ~200-250 if done in full, plus 2-3 rounds of policy and webhook iteration). Treat all estimates as ranges — no documented exact-match Lovable auction build exists to triangulate against. total
ItemCost
Lovable Pro

Can drop to Free after launch if you're not iterating

$25/mo
Lovable Cloud / Supabase Free

Free tier includes 200 concurrent Realtime connections — enough for ~200 simultaneous spectators on one auction

$0/mo at MVP scale
Supabase Pro (when needed)

Bump to Pro when you outgrow 200 Realtime connections or 500MB DB

$25/mo
Stripe

Per winning bid settled — no monthly fee

2.9% + 30¢ per charge
Resend

Free up to 3,000 emails/month

$0/mo
Custom domain$10-15/yr

Scaling notes: 200 concurrent Realtime connections is the first ceiling — if a single auction draws more than 200 simultaneous viewers, additional viewers silently fail to subscribe and see a stale bid display. Upgrade to Supabase Pro ($25/mo) for 500 concurrent connections. The 500MB DB is the second ceiling — bids tables grow fast on high-volume auction days. Stripe per-charge fees are unavoidable and scale linearly with settlement volume.

Production checklist

Steps to take before you share the URL with real users.

Domain & SSL

  • Publish to a custom domain

    In Lovable, click the Publish icon (top right) → Settings → Custom Domain → enter your domain and follow the CNAME DNS instructions. SSL is provisioned automatically.

  • Update Stripe webhook endpoint to production URL

    In Stripe Dashboard → Developers → Webhooks → Add endpoint, enter https://yourdomain.com/api/stripe-webhook. Copy the new signing secret to Cloud tab → Secrets as STRIPE_WEBHOOK_SECRET.

Fraud & Security

  • Test place_bid atomicity with two browser tabs

    Open two incognito windows, sign in as two different non-admin users, both bid the same amount simultaneously. Only one should succeed. If both receive receipts, the SELECT FOR UPDATE lock is not working — re-check the RPC.

  • Verify RLS with a non-admin account

    Sign in as a regular bidder (not the Lovable preview account which uses service-role). Attempt a direct Supabase JS call to insert into bids without going through place_bid. Should receive permission denied.

Backups & Monitoring

  • Enable daily database backups

    Cloud tab → Database → Backups — confirm daily backups are enabled. On Supabase Free, backups are retained for ~7 days. Upgrade to Pro for point-in-time recovery.

  • Set up Stripe fraud protection

    In Stripe Dashboard → Radar → Rules, enable the default rules for card testing and high-risk payments. For an auction platform, also add a rule to block cards used on more than 3 failed attempts in 24 hours.

Stripe Go-Live

  • Switch from test to live Stripe keys

    In Stripe Dashboard, activate your account (provide tax ID + bank details). Update STRIPE_SECRET_KEY in Cloud Secrets to sk_live_... and STRIPE_WEBHOOK_SECRET to the live webhook signing secret. Test with a real $1 charge on your own card and immediately refund.

Frequently asked questions

Can I run this on the Free Lovable plan?

You can start on Free for the first few prompts, but the full auction build will exceed the 30-credit monthly cap quickly. The place_bid RPC alone takes 40+ credits to get right, and Stripe wiring adds another 60-80. Plan on Pro $25/mo from the start and budget 1-2 top-ups. The Free plan's 200 concurrent Realtime connections cap is fine for a soft launch.

Why can't I just insert into the bids table directly from React?

Two reasons. First, two browser tabs can send bid requests simultaneously, and without a SELECT FOR UPDATE lock inside a database transaction, both writes can succeed — meaning two bidders both get a receipt claiming they won with the same amount. That is a real money dispute. Second, a determined user can open DevTools, call the Supabase JS client directly, and send any amount they want — bypassing your React validation entirely. The place_bid RPC running inside Postgres with server-enforced rules is the only correct pattern.

What happens if the Stripe charge fails after the auction closes?

The charge-winner function sets payments.status to 'failed' and sends the winner a payment-link email so they can complete the purchase manually via a hosted Stripe checkout. In /account → Won Items, the winner sees a 'Complete payment' button. If they don't pay within 48 hours, you can cancel the sale and relist — add an admin action in /admin/moderation for this. Do not attempt automatic retries for charge failures; Stripe's built-in retry logic handles declined cards on subscriptions but not one-time PaymentIntents.

How do I handle bidder cancellation or non-payment?

There is no automatic non-payment resolution in v1. You need an admin workflow: in /admin/moderation, add a 'Cancel sale + relist' action on any auction in the closed state with a pending payment. This resets the auction status to 'draft' and lets the seller edit and relist. For repeat non-payers, add a blocked_bidders table and check it in place_bid — the anti-shilling follow-up prompt is the right place to add this.

Can I run multiple auctions simultaneously without overwhelming the Realtime tier?

Yes, but watch the connection count. Each auction detail page open by a user consumes 2 Realtime connections (one for the auctions channel, one for the bids channel). The Free Supabase tier allows 200 concurrent connections total across all auctions. If you run 10 simultaneous auctions with 10 viewers each, that's 200 connections — at the cap. Upgrade to Supabase Pro ($25/mo) for 500 connections when your auction volume grows. The 30-second polling fallback in the Realtime follow-up prompt acts as a safety net for spectators who can't get a connection.

Is this legal — do I need an auctioneer license?

Auctioneer licensing requirements vary significantly by jurisdiction. In the United States, most states require a license for live or online auctions of certain goods, especially real property, art, and livestock. Running a closed B2B surplus or charity auction for your own inventory is generally lower-risk. This page builds the software, not legal advice — consult an attorney familiar with auction law in your jurisdiction before opening to the public, especially if you plan to handle high-value goods or take a percentage of sales.

Can RapidDev build a production auction platform for me?

If your build outgrows this prompt kit and you need custom architecture, RapidDev builds production-grade Lovable apps at $13K-$25K — book a free 30-minute consultation at rapidevelopers.com.

Need a production-grade version?

RapidDev builds production-grade Lovable apps at $13K–$25K. Book a free 30-minute consultation.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.