Checked Backend Logic2 tables · 5 policies · 7 checks

    Internal Admin Dashboard template for Lovable

    Role-gated metrics and an append-only event log, with the admin check in the database. 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 Internal Admin Dashboard kit?

    A security definer function runs with its owner's rights and skips row-level security, so every caller counts every row. The kit's function runs as the caller, so a member counts only their own events.

    The mistake

    An aggregate function that leaks the event log: the policy Lovable writes when nobody names the rule.

    The check that catches it

    verify.sql, check 5: Should count ONLY your own events. If this matches the true table size, the aggregate function was made SECURITY DEFINER by mistake.

    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 Internal Admin Dashboard 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.

    The verification checks in the kit's verify.sql, as written in its comments
    #Expected result
    1Should return only your own events, never the whole table.
    2Should return only your own role row.
    3Should FAIL. There is no insert policy on user_roles, so nobody self-promotes.
    4Should FAIL. No update policy on events.
    5Should count ONLY your own events. If this matches the true table size, the aggregate function was made SECURITY DEFINER by mistake.catches the trap
    6Should now return other users' rows too.
    7Confirm RLS is on for both tables.
    Lovable's Security view with the Deep security scan and Quick security scan cards and a No issues found panel, beside the chat reporting a clean scan
    Lovable's free scans on my SaaS project, 26 September 2026. A clean scan says row-level security is switched on; verify.sql shows what the policies deny.

    What is in the kit?

    • A build prompt that requires a window on every metric and distinguishes zero from unknown
    • A schema with a separate user_roles table and an events log
    • RLS where a member sees only their own events and an admin sees all of them
    • No insert policy on user_roles at all, so nobody can self-promote from the client
    • An aggregate function deliberately left non-definer, so it respects the caller's policies
    • Seven verification queries, run once as a member and once as an admin

    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.

    1. 01

      Run schema.sql in the Supabase SQL editor

      Creates the role type, both tables and the policies.

    2. 02

      Grant yourself admin from the SQL editor

      The commented statement at the end of schema.sql. Deliberately not an endpoint, because a self-service role grant is a privilege escalation.

    3. 03

      Run verify.sql as a member first

      The member results prove the policies. Running only as an admin proves nothing.

    4. 04

      Paste prompt.md into Lovable

    Things that catch people out

    • Roles live in their own table, so the read policy on user_roles can differ from the write policy. A role column on a user-editable profile row is self-service privilege escalation.
    • event_counts_by_day() is intentionally not SECURITY DEFINER. If you add that keyword to make a chart work for non-admins, you have exposed the whole event log. Verification query 5 catches it.
    • There is no update or delete policy on events, because an audit log that can be rewritten is not one.

    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.

    The build prompt

    # Context
    Build an internal admin dashboard showing what is happening in the product now, for an operator on shift. Every number carries its time window.
    
    ## Core Features (Priority Order)
    1. Admin-only access: the page calls has_role with 'admin' before rendering any data
    2. Four tiles from events, window stated in each: events, errors, warnings and distinct users in the last 24 hours
    3. Chart from event_counts_by_day with a 7, 30 or 90 day picker, default 7
    4. Recent events from events, newest first, 25 per page; a row opens its payload in a side sheet
    5. Separate screens for "no events yet" and "query failed"
    
    ## Visual Style & Design
    - Data-driven and analytical: dense, right-aligned tabular numbers
    - Dashboard layout: four tiles, chart, then the table scrolling in its own container
    - IBM Plex Sans for headings and body text, IBM Plex Mono for ids and payloads
    - Colours: background #0b0f14, panels #131a22, text #e5e7eb, accent #38bdf8; severity info #94a3b8, warn #f59e0b, error #ef4444
    - shadcn/ui Card for tiles, Table for events, Tabs for range, Badge for severity, Sheet for the payload
    - Save these style keywords to the project knowledge.
    
    ## Signed In and Signed Out
    Signed-out visitors go to sign-in. A signed-in user without the admin role sees "You do not have access to this page" and no events query runs.
    
    ## States
    - Empty: "No events in the last 24 hours" in each tile and on the chart
    - Loading: Skeletons sized to the final content
    - Error: a failed query shows a dash in the tile, never 0, with "Could not load" and a retry button
    
    ## Data & Access
    - Tables user_roles and events, the app_role type, has_role, event_counts_by_day 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.
    - Reads run as the signed-in user, so the policies decide what each user sees. event_counts_by_day never becomes security definer.
    - Events are append-only. user_roles has no insert policy, so no screen grants roles; I grant admin in the SQL editor.
    
    ## Done When
    - Signed out, /admin redirects to sign-in
    - Signed in with no admin row in user_roles, /admin shows the no-access screen and an events query from that session returns only that account's rows
    - A browser attempt to insert an admin row into user_roles for the current user is rejected
    - Signed in as admin, the 7-day chart totals match event_counts_by_day for 7 days and the table shows other users' events
    - A forced query failure shows the dash and retry in place of 0
    
    ## Build Order
    Sign-in, the route guard and has_role before any query, one tile end to end, the other tiles and table, the chart last.
    
    ## Safe-Guard Instructions
    - No policy that lets non-admins read all events, including for testing
    - No service role key in client code
    - Keep the window label on every number
    
    ## 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 bundle

    Roles, events and policies

    -- Internal dashboard schema for Lovable + Supabase
    -- Reviewed row-level security. Read the comments before running this.
    --
    -- The point of this file: role checks live in the database, not only in the UI. A generated
    -- dashboard that hides a menu item is not access control.
    
    create type public.app_role as enum ('admin', 'member');
    
    create table public.user_roles (
      user_id uuid not null references auth.users (id) on delete cascade,
      role public.app_role not null default 'member',
      created_at timestamptz not null default now(),
    The rest of schema.sql, every policy written and its denials verified, is in The Complete Lovable Bundle.Get the Bundle

    verify.sql

    Preview · full file in the bundle

    Policy verification queries

    -- Verification queries. Run these as an ordinary member first, then as an admin.
    -- The member results are the ones that prove the policies work.
    
    -- As a MEMBER (no admin role):
    
    -- 1. Should return only your own events, never the whole table.
    select id, name, user_id from public.events order by created_at desc limit 20;
    
    -- 2. Should return only your own role row.
    select * from public.user_roles;
    
    -- 3. Should FAIL. There is no insert policy on user_roles, so nobody self-promotes.
    The rest of verify.sql, every policy written and its denials verified, is in The Complete Lovable Bundle.Get the Bundle

    After the kit

    Which library prompts build on the Internal Admin Dashboard kit?

    Each extends the kit's tables with one feature, keeps its policies, and ends with Done When checks Lovable can verify.

    • Roles Table and the Admin Route Gate

      The access layer of an internal admin dashboard, where the role is read from the database on every request and the first admin is granted only by SQL.

    • Metrics Overview from Windowed Views

      An admin metrics overview with 4 windowed cards and an events chart, computed by database views that respect the caller's role and show Unknown apart from zero.

    • Append-Only Event Log Viewer

      A viewer for the append-only events table with filters kept in the URL, a payload detail pane, admin-only CSV export, and no path to edit or delete.

    • User Management with Audited Role Changes

      Admin user management where role changes go through one function that needs a reason, writes a role_changed event, and refuses to demote the last admin.

    • Moderation Queue with Recorded Decisions

      A moderation queue where moderators claim items for 15 minutes, decide with a required reason, and rejected content is hidden by status and kept for appeal.

    • Read-Only View-as-User for Support

      A 15-minute read-only view-as-user mode for support, with a persistent banner, a required reason, and a sessions list the viewed user can see.

    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 Internal Admin Dashboard template for Lovable include?

    A build prompt that requires a window on every metric and distinguishes zero from unknown. A schema with a separate user_roles table and an events log. RLS where a member sees only their own events and an admin sees all of them. No insert policy on user_roles at all, so nobody can self-promote from the client. An aggregate function deliberately left non-definer, so it respects the caller's policies. Seven verification queries, run once as a member and once as an admin.

    What mistake does the Internal Admin Dashboard kit prevent?

    An aggregate function that leaks the event log. A security definer function runs with its owner's rights and skips row-level security, so every caller counts every row. The kit's function runs as the caller, so a member counts only their own events.

    How do I prove the Internal Admin Dashboard policies work?

    Run verify.sql after schema.sql, as an ordinary signed-in user and never as service_role, which bypasses every policy. Check 5 is the one that catches the trap: Should count ONLY your own events. If this matches the true table size, the aggregate function was made SECURITY DEFINER by mistake.

    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

    Checks parsed from the kit files; page checked 23 October 2026.

    Written by

    Marco Kohns

    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.