Postgres in the Next.js export: your data, guarded by the database itself
A site published on Anticode keeps its form submissions in Anticode's own storage. When you export the build as a Next.js project, that changes: bookings, messages, newsletter sign-ups and accounts go into a Postgres database you run, through Prisma, and every table has Postgres row-level security switched on. This page explains what the export creates and the one database setting that decides whether those protections apply.
Hosted site or exported app
| Published on Anticode | Next.js export | |
|---|---|---|
| Storage | Anticode's storage, submissions sealed at rest with AES-256-GCM | Your Postgres database |
| Who runs it | Anticode | You, on any Postgres host |
| Accounts | None for visitors; the sign-up form is an invite request | Owner, admin, staff and customer roles |
| Backups | Anticode keeps the last ten published versions of the site | Whatever your database host provides |
The export starts with an empty database. Submissions your hosted site already collected stay in the Anticode dashboard, where you can read and export them; they aren't copied into the new tables. Choosing a website builder Postgres export is worth it when you need real customer accounts, your own queries, or a developer extending the data model.
The tables
- Booking
- service, day (YYYY-MM-DD), time (HH:MM), name, phone, email and a status that defaults to confirmed
- Message
- contact-form name, email and body
- Subscriber
- newsletter emails, one row each
- User
- accounts with a role (OWNER, ADMIN, STAFF, CUSTOMER), a scrypt password hash and encrypted two-factor secrets
- Session and LoginToken
- only SHA-256 hashes of session cookies and sign-in links, never the raw tokens
- RateLimit
- counters for the forms' rate limits, kept in Postgres
- AuditLog
- an append-only record of who did what, readable by owners
How the row-level security works
Migration 0002_row_level_security enables and forces row-level security on every table, then adds policies keyed to a role the app declares. At the start of each transaction, the helper asRole in lib/db.ts calls set_config('app.role', …, true) and, for customers, app.email, with bound parameters and scoped to that transaction only. The policies then decide inside Postgres what each role may do:
- public (an anonymous visitor) may insert bookings, messages and subscribers but read none of them. Slot availability is counted by a database function so visitors never see other people's bookings.
- customer may read only bookings whose email matches their own.
- staff may read bookings, messages and subscribers; the app masks emails and phone numbers for them.
- admin may also update and delete them; owner may also manage the team and read the audit log.
- system is the sign-in core: sessions, tokens and rate limits, never page data.
The point is defence in depth. If a future change forgets a where clause, Postgres returns nothing rather than someone else's data. The migration's own comments are honest about the boundary: it guards against application bugs, not against someone who can run arbitrary SQL as the app's user.
The check that matters: does your role bypass RLS?
FORCE ROW LEVEL SECURITY makes policies apply to a table's owner, but the PostgreSQL documentation is clear that superusers and roles with the BYPASSRLS attribute always bypass row security. Some hosted Postgres services give their default roles exactly that. Neon, for example, documents BYPASSRLS among the privileges of its neon_superuser role, which console-created roles belong to.
So after connecting, run SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user;. If either is true, the app still works and its own role checks still apply, but the database-level guard is off. To get it back, create a separate login role in SQL without those attributes, grant it the table privileges the app needs, and put that role in DATABASE_URL, keeping the owner role for running migrations.
Choosing a host
Anything that speaks standard Postgres works. The project's README describes a free hosted database from Neon or Supabase and a local one in Docker; the example environment file also mentions RDS and Railway. Host-specific notes are on the Neon and Supabase pages, and the schema and commands are covered under Prisma.
Limits
- You run the database: backups, upgrades and access control are yours.
- There's no sync back to Anticode. Changes in the exported app don't reach the builder.
- There are no payment or order tables; add them yourself if you sell through the app.
- Prisma is pinned to version 6 in the template.
Questions people ask
Can a published Anticode site use my Postgres database?
No. Hosted sites use Anticode's storage. Your own database comes with the Next.js export.
Is row-level security enough on its own?
No single layer is. The app validates input, checks roles and masks data too; RLS catches what slips past those.
Does the export include my existing submissions?
No. It creates empty tables. Export past submissions from the Anticode dashboard if you need them.
Sources
- PostgreSQL documentation: Row security policies, checked
- Neon Docs: Manage roles, checked