Database and security
The 53 tables and what is missing from them, row level security everywhere, the four staff roles, and the two views your public site reads.
Your code
Your Supabase project holds 53 tables, created by 30 migration files, with row level security enabled on every one of them and 117 access policies in total.
The migrations live in supabase/migrations/ in your repository. They are applied one by one at publishing, and each one is recorded as soon as it passes, so none is ever replayed.
The tables, by family
| Family | Tables |
|---|---|
| Catalogue | products, product_variants, product_options, product_option_values, product_attribute_values, attributes, attribute_values, categories, product_categories, collections, product_collections, plus one metadata table per product type: bundle, digital, gift card, service, subscriptions |
| Stock | inventory_locations, inventory_stock, inventory_movements |
| Commerce | orders, order_items, order_status_transitions, customers, customer_segments, customer_segment_members, coupons, coupon_redemptions, product_coupons, reviews |
| Delivery | shipping_zones, shipping_methods, shipping_carriers |
| Payment | payment_providers, payment_transactions, stripe_events |
| Content | blog_posts, blog_categories, blog_comments, blog_post_tags, blog_post_seo, product_seo, category_seo, marketing_page_seo |
| Measurement | analytics_events, ab_tests, gsc_connection, gsc_metrics |
| Team and settings | staff_users, staff_roles, settings, branding, integrations, integration_connections |
All money is stored as whole cents, in bigint columns suffixed _cents. There are no decimal amounts anywhere in the schema.
Demonstration data is deliberately absent: the commerce seed migration is empty. A new project starts with no fake customer, no fake order and no fake coupon.
What the schema does not hold
That table is the whole schema, not a sample of it. There is no invoices table, and no quotes, payments, appointments, bookings, availability or addresses either.
This matters when an agent reads your data. Its read tool carries a list of table names it is allowed to open, and that list is wider than your schema. Ask an agent about your invoices and the read comes back as a database error, not as an answer.
What an agent can actually read is what the families above create: products, orders, customers, stock, coupons, reviews, blog posts. Anything else has to be built first, in a generation that writes the migration and applies it to your project.
Who can do what
Four staff roles are ranked: viewer 1, editor 2, admin 3, owner 4. The function require_staff_role(min_role) raises an error when the caller does not hold at least the rank asked for.
Server side calls made with the service role key run before that check. That is why the key belongs in environment variables and nowhere else.
The first person to sign in becomes owner
The back office signs in against your own Supabase Auth, with email and password or a magic link. There is no sign up form, and MaShop creates no account for you: the first magic link you ask for creates the account.
While no role exists yet, that first signed in account is promoted to owner by the claim_first_owner function. A transaction lock guards it, so two owners cannot appear at once. Everyone else is added from Settings, then Staff.
Publish, then claim your back office. An address that is online and unclaimed is an address anyone who finds it can claim.
What the public can read
The public site never reads your tables. It reads two views: storefront_products and storefront_categories. The read grant is given to anon and authenticated on the views only; the tables stay closed.
storefront_products exposes only products whose status is active, and computes what a page needs: the displayed price, which is the cheapest positive variant or else the base price, the compare at price, the main image, the full gallery, availability and the main category. storefront_categories exposes only categories flagged as published.
The availability rule inside the view is worth knowing before you debug a product that looks out of stock:
- A product not tracked in stock is always buyable.
- A tracked product with no variant at all counts as available, because that is missing data rather than a zero.
- As soon as it has variants, the sum of their stock decides.
Adding a column to a public view
Postgres matches the columns of a replaced view by position, not by name. A column inserted in the middle of storefront_products or storefront_categories makes the migration fail, and that failure stops publishing at the schema step.
This is not theoretical. Between 28 July and 17 August 2026, no newly created project could get its database for exactly that reason. Any new column goes last, always.
File storage
Six storage buckets are created with the schema: product-images public, digital-assets private, category-images, blog-covers, branding-assets, seo-images.
Those buckets live in your Supabase project, like the tables. Deleting a project inside MaShop does not touch them.
