Lightweight CRM template for Lovable
Companies, contacts and an append-only note timeline: shared team reads, owner-only writes. The kit is three files: a build prompt for Lovable, a Supabase schema with row-level security, and verification queries that run as the wrong user to prove each policy denies what it should. The prompt is free; the full SQL comes with the bundle.
By Marco Kohns · Stack: React + Vite, TypeScript, Tailwind CSS, shadcn/ui, Supabase
The trap
What goes wrong without the Lightweight CRM kit?
Without with check, the owner passes the first test and then writes a new owner_id, handing the record away. The kit checks ownership before and after the write.
The mistake
A record reassigned to someone else: the policy Lovable writes when nobody names the rule.
The check that catches it
verify.sql, check 4: Should FAIL or affect zero rows. You cannot reassign a record to yourself.
The policies that prevent it are in the kit's schema.sql, which ships with the bundle; the hub shows one pattern per kit.
verify.sql
How do you prove the Lightweight CRM policies work?
Run every check as an ordinary signed-in user, never as service_role, which bypasses every policy and makes a broken schema look correct.
| # | Expected result |
|---|---|
| 1 | Should return the whole team's contacts. This schema is deliberately shared. |
| 2 | Should SUCCEED, and owner_id should default to you without being supplied. |
| 3 | Should FAIL or affect zero rows. You cannot edit someone else's record. |
| 4 | Should FAIL or affect zero rows. You cannot reassign a record to yourself.catches the trap |
| 5 | Should FAIL. A note must be attributed to its actual author. |
| 6 | Confirm RLS is on for all three tables. |

What is in the kit?
- A build prompt that puts speed of entry first
- A schema with companies, contacts, notes and a pipeline stage enum
- Shared team reads with owner-only writes, and a with-check clause that blocks reassignment
- A GIN full-text index so search works without a separate search service
- Six verification queries, including one proving user B cannot steal user A's record
What the kit does not do
- Not a pre-built app you clone and run. There is no repository and no node_modules.
- Not a design file or a theme. Lovable generates the interface from the prompt.
- Not a substitute for reading the SQL. You are running it against your own database.
Setup
In what order do you set up the kit?
Schema before prompt, because the prompt references the table names the schema creates.
- 01
Decide shared or private before running schema.sql
This schema is a shared team CRM: every authenticated user reads every record. For per-user isolation, change the select policies to owner_id = auth.uid() first.
- 02
Run schema.sql in the Supabase SQL editor
- 03
Run verify.sql as two different users
Queries 3 and 4 only mean anything when run by someone who does not own the record.
- 04
Paste prompt.md into Lovable
Things that catch people out
- owner_id defaults to auth.uid() in the schema, so the client never sends it. So ownership is derived on the server, and the browser never chooses it.
- The with check clause on the update policies is the part people omit. Without it an owner can update their own row and set owner_id to somebody else, or a user can claim a record by rewriting the owner. Verification query 4 tests exactly that.
- Shared reads are a deliberate default for a sales team. It is the one policy decision in these templates to confirm on purpose before running it.
The files
What do the kit files contain?
The build prompt in full, and the first 12 lines of each SQL file so you can judge the code before you run it anywhere.
prompt.md
Download prompt.mdThe build prompt
# Context
Build a lightweight shared CRM for a small sales team, typically a founder doing their own sales, tracking companies, people and what was said. Speed of entry matters most, so common actions work from the keyboard.
## Core Features (Priority Order)
1. Contact list from contacts, searched instantly over loaded rows by full_name, email and company, with a server query on full_name and email beyond the loaded page
2. Contact detail with a note timeline from notes, newest first
3. Add a note in two keystrokes (N, then Cmd+Enter), no modal
4. Companies from companies, listing contacts through contacts.company_id
5. Stage from pipeline_stage (new, contacted, qualified, proposal, won, lost), changed inline by the record's owner
6. Owner shown on every record
## Visual Style & Design
- Minimal and focused: dense list, roomy detail, keyboard hints
- Split screen: contact list on the left, detail on the right; below md the list becomes cards
- Geist for headings, Inter for body text
- Colours: background #ffffff, text #111827, muted #6b7280, accent #7c3aed; one colour per stage, identical in list and detail
- shadcn/ui Table, Command for search, Badge for stage, Avatar, Textarea for notes, Select for stage
- Save these style keywords to the project knowledge.
## Signed In and Signed Out
Signed-out visitors see sign-in only. Signed-in users see the whole team's records, with edit controls only on records they own.
## States
- Empty: distinct messages for no contacts ("Add your first contact"), no search results and no notes
- Loading: Skeleton rows in list and timeline
- Error: a stage change updates optimistically and reverts with a toast; a failed note keeps its text with retry
## Data & Access
- Tables companies, contacts and notes, the pipeline_stage type, the touch_updated_at trigger and every row-level security policy already exist in the connected Supabase project from schema.sql. Never weaken, drop or bypass them or create tables; if something is missing, stop and ask me.
- Every signed-in user reads every record; only the owner updates or deletes a company or contact, only the author a note.
- The client never sends owner_id or author_id; defaults set them from auth.uid().
## Done When
- A contact created without an owner field shows the signed-in user as owner
- Signed in as a second account, the first account's contacts are visible with stage and edit controls disabled, and a forced stage update changes zero rows
- A forced update setting owner_id on another user's contact changes zero rows
- A note saves with the signed-in user as author and appears at the top of the timeline
- Signed out, /contacts redirects to sign-in
## Build Order
Sign-in and the contact list, detail and timeline, note creation with its failure path, companies, the inline stage change last.
## Safe-Guard Instructions
- Notes stay append-only in the UI: no edit, bulk delete or reassignment controls
- Ask me before opening sign-up beyond the team, because every signed-in account reads every record
- No service role key in client code
## Before Building
Ask me any questions you need in order to fully understand what I want from this feature and how I envision it. Save my answers to the project knowledge so later prompts build on them.schema.sql
Preview · full file in the bundleCompanies, contacts, notes and policies
-- Lightweight CRM schema for Lovable + Supabase
-- Reviewed row-level security. Read the comments before running this.
--
-- Model choice worth understanding: this is a SHARED team CRM. Every authenticated user can
-- read every record, because a sales team that cannot see each other's pipeline is not
-- useful. Writes are restricted to the owner. If you need per-user isolation instead, change
-- the select policies to owner_id = auth.uid() and re-run the verification queries.
create type public.pipeline_stage as enum
('new', 'contacted', 'qualified', 'proposal', 'won', 'lost');
create table public.companies (verify.sql
Preview · full file in the bundlePolicy verification queries
-- Verification queries. Run these as user A, then sign in as user B and repeat 3 and 4.
-- 1. Should return the whole team's contacts. This schema is deliberately shared.
select id, full_name, stage, owner_id from public.contacts limit 20;
-- 2. Should SUCCEED, and owner_id should default to you without being supplied.
insert into public.contacts (full_name, email) values ('Verify Person', 'v@example.com');
select full_name, owner_id = auth.uid() as owned_by_me
from public.contacts where email = 'v@example.com';
-- As user B, against a contact owned by user A:
After the kit
Which library prompts build on the Lightweight CRM kit?
Each extends the kit's tables with one feature, keeps its policies, and ends with Done When checks Lovable can verify.
- Contacts and Companies with Dedupe
The contacts and companies layer of a lightweight CRM, where a matching email or company name shows before saving and owner_id comes from the database default.
- Pipeline Board with Stages You Define
A deal pipeline board where one server function owns every stage change and appends a stage_history row, with amber and red badges for stuck deals.
- Activity Timeline That Takes Seconds to Write
An append-only activity timeline on the CRM's notes table, with a composer that saves on Enter and a company feed that merges every contact's notes.
- Follow-Up Reminders and a Morning Digest
Dated follow-ups on contacts and deals with a Today view, inline snooze, a Gone quiet list after 14 silent days, and a daily digest email.
- Public Lead Form Writing Into the CRM
A public lead form that writes into the CRM's leads table insert-only, captures UTMs on the server, and queues each lead for a member to accept or discard.
- CSV Import with Mapping and Dedupe Preview
A CSV import for CRM contacts and companies that marks every row Create, Match existing or Skip before writing, and undoes a batch while no record has notes.
The full SQL for all 4 kits
The Complete Lovable Bundle: every schema.sql and verify.sql, every Pro prompt, unlimited generation. One payment. No subscription, no renewal, ever.
Disclosure: if you create a Lovable account through links on this page, we may earn a commission. It costs you nothing and does not change what we recommend. Create a free Lovable account
Frequently asked
What does the Lightweight CRM template for Lovable include?
A build prompt that puts speed of entry first. A schema with companies, contacts, notes and a pipeline stage enum. Shared team reads with owner-only writes, and a with-check clause that blocks reassignment. A GIN full-text index so search works without a separate search service. Six verification queries, including one proving user B cannot steal user A's record.
What mistake does the Lightweight CRM kit prevent?
A record reassigned to someone else. Without with check, the owner passes the first test and then writes a new owner_id, handing the record away. The kit checks ownership before and after the write.
How do I prove the Lightweight CRM policies work?
Run verify.sql after schema.sql, as an ordinary signed-in user and never as service_role, which bypasses every policy. Check 4 is the one that catches the trap: Should FAIL or affect zero rows. You cannot reassign a record to yourself.
What we did not publish
- A Lovable credit cost for this kit. The SQL was validated against Postgres 17; the kit was not built end to end in Lovable for this page.
- The SQL in full; it is what the bundle sells.
- The mistake SQL is an illustration written for this page; the kit SQL beside it is quoted from schema.sql.
These templates are provided free and as-is, with no warranty of any kind. The SQL is intended to be run against your own Supabase project and will create and alter tables. Read it, run it on a development project first, and review every policy against your own requirements before going near production data. You remain responsible for the security and correctness of anything you ship. These schemas assume Supabase, which grants the anon and authenticated roles access to new tables in the public schema by default. Row-level security is what then restricts those roles. If you run this SQL on a plain Postgres instance instead, those grants do not exist, so the policies appear to work while the missing privileges are what blocks access, and a test there proves nothing about the policies.
Other kits
- SaaS Starter · a member promoting themselves to owner
- Landing Page with Email Capture · a public form that exposes the whole list
- Internal Admin Dashboard · an aggregate function that leaks the event log
Checks parsed from the kit files; page checked 23 October 2026.
Written by

Marco Kohns
Founder of ProtoBites - Venture Growth Studio
Growth PM at a Silicon Valley scale-up (a16z and General Catalyst backed), ex-Techstars where he consulted 13 early-stage startups, Reforge-trained. Every prompt on this site comes out of shipping ProtoBites' own portfolio products.
Related reading
- all four build kits · SaaS, landing, dashboard.
- the CRM-lite contacts prompt · In the app builder category.
- backend prompts · Soft delete and recovery for records.
- the CRM sync integration prompt · Two-way sync done safely.
- the SaaS starter kit · Workspaces to put the CRM inside.
- the admin dashboard kit · Reporting on the same rows.
- the access-control mistake · Reassignment blocked by policy.
- Lovable vs Replit · The database incident to design against.
- Replit alternatives · The builder route, measured in credits.
- Lovable and Supabase · The docs behind this kit's policies.
- Lovable security · Coverage versus correctness, from the CVE.
- CRM app prompts · Ten briefs written to this kit's schema.