Anticode Open builder

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.

Updated · By the Anticode team

Hosted site or exported app

Where the data lives
Published on AnticodeNext.js export
StorageAnticode's storage, submissions sealed at rest with AES-256-GCMYour Postgres database
Who runs itAnticodeYou, on any Postgres host
AccountsNone for visitors; the sign-up form is an invite requestOwner, admin, staff and customer roles
BackupsAnticode keeps the last ten published versions of the siteWhatever 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

  1. PostgreSQL documentation: Row security policies, checked
  2. Neon Docs: Manage roles, checked