Skip to main content

Read and write Supabase data

Your app's data lives in tables in your Supabase database, a bit like spreadsheets: a todos table has a column for each detail, such as the title and whether it's done, and a row for each to-do. Your app works with those rows through queries, requests such as "give me all my to-dos". Query Templates turn a table into ready-made functions that run them, Nowa AI writes any other query you need, and you can test each one right in the editor before you build a screen around it.

Before you start​

Create a table​

Nowa has no table editor, so you create tables in Supabase, or let Nowa AI do it for you. Either way, think about who may see each row. Row-level security (RLS) is Supabase's lock on a table: once it's on, your app sees nothing until you add policies, rules such as "people can only see and change their own to-dos".

With Nowa AI​

This is the quickest way. Turn on the Supabase connector with the Supabase icon in the chat field, switch to Agent mode and describe the table and who may use it, for example: "Create a todos table with a title and a done column, where each signed-in user can only see, add, change and delete their own rows." Nowa AI shows you each change for approval before it runs it, unless Auto-approve tools is on. Then open ⋮ → Tables in the Supabase panel to see the new table. See Connect Figma and Supabase to Nowa AI.

In the Supabase dashboard​

Prefer to set it up yourself? Supabase's SQL Editor runs ready-made instructions for you, so you can create the table and its rules in one go:

  1. In Nowa, click ⋮ in the Supabase panel, then Open Supabase.
  2. Open the SQL Editor.
  3. Paste the snippet below and click Run.
  4. Open the Table Editor to check that todos is there.
-- A to-do list where each signed-in user only sees their own rows
create table todos (
id bigint generated by default as identity primary key,
created_at timestamptz not null default now(),
user_id uuid not null references auth.users default auth.uid(),
title text not null,
done boolean not null default false
);

-- Let signed-in users and the full-access key reach the table
grant select, insert, update, delete on table todos to authenticated, service_role;

-- Turn row-level security on, then add one policy per action
alter table todos enable row level security;

create policy "Users can view their own todos" on todos
for select to authenticated using ((select auth.uid()) = user_id);

create policy "Users can add their own todos" on todos
for insert to authenticated with check ((select auth.uid()) = user_id);

create policy "Users can update their own todos" on todos
for update to authenticated using ((select auth.uid()) = user_id);

create policy "Users can delete their own todos" on todos
for delete to authenticated using ((select auth.uid()) = user_id);

In plain words: it creates the table with an automatic ID, the time each row was created and the user it belongs to (filled in for you), lets signed-in users reach it (and the full-access key, which Nowa uses when it lists your tables), turns RLS on, and adds one policy each for reading, adding, changing and deleting, all limited to the user's own rows.

Prefer clicking? In the Table Editor, click New table, enter a Name (lowercase, with underscores instead of spaces, such as shop_items), add your columns with Add column, and click Save. Supabase turns RLS on for you and adds id and created_at columns. Then ask Nowa AI for the policies, for example: "Add policies to the shop_items table so anyone can read it and only signed-in users can add rows."

Supabase is also changing how new tables are shared: on newer projects, and on every project from 30 October 2026, a new table doesn't let your app in until access is granted. Without that access, tests show RLS Policy Error even when your policies are right. The snippet above grants that access. If a table you made in the Table Editor shows this, ask Nowa AI, for example: "Give signed-in users access to the shop_items table."

Tables you make in the SQL Editor or the Table Editor aren't recorded as migrations (the saved, step-by-step history of how your tables were built), so Pull Backend Files can't save them into your project later. If you plan to share or copy your backend, ask Nowa AI to create the tables through migrations instead (see Manage your Supabase backend).

Add a query from a template​

  1. In the Supabase panel, click + next to Generate a Query. The Add Supabase Function dialog opens.
  2. Click Query Templates ("CRUD operations": create, read, update and delete). If you see No Tables Found, click Fetch Tables. Created a table after you connected? Open ⋮, then Tables, which refreshes the list. Connected with Use Keys and still no tables after Fetch Tables? Authorize Nowa once with Connect (see Connect with keys).
  3. Click a template from the table below.
  4. Click the table you want. You can search the list.
  5. Choose a model. Click Create new model class (Nowa suggests a name such as TodosModel) or pick one under Use Existing Model. Nothing is selected until you click.
  6. Click Generate Function. The message Function "getAllTodos" generated successfully! appears, and the function shows up under Queries, ready to test.

The Supabase Templates dialog on the Choose Model For "todos" step: under Create New Model, the Create new model class option is selected with TodosModel filled in as its name (highlighted), and the Generate Function button at the bottom (highlighted).

TemplateWhat it doesFunction for a table called todos
Get All RecordsFetches all records from a tablegetAllTodos() returns a list of your model
Get Record by IDFetches a single record by its IDgetByIdTodos(id) returns one model, or nothing if no row matches
Create RecordCreates a new record in the tablecreateTodos(data) returns the new row
Update RecordUpdates an existing record by IDupdateTodos(id, data) returns the updated row
Delete RecordDeletes a record by IDdeleteTodos(id)

Each function is named after its action and the table, with the first letter of the table name capitalized. That's the name you'll pick in Circuit and in a Data Builder. The ID templates look for a column named id, and its type decides whether id is a number or text.

The templates don't filter, sort or page the results. For that, ask Nowa AI (see the next section) or change the code with Edit Code. Generating the same template for the same table again replaces the earlier function. For the other templates on that table, pick the model you already made under Use Existing Model, so they share it.

A data model describes one row of your table: it has one field per column, so your screens can show title or done directly. Nowa maps each column by the type Supabase reports: string columns become String (text), integer columns int (whole numbers) and boolean columns bool (yes or no). Any other type becomes dynamic, which can hold any value, and every field can be empty. Use Existing Model lists the data models already in your project. See Data models.

Before you test Create Record or Update Record, know that they send every field of the data model, including the ones you leave empty, and Supabase doesn't fill in an empty field for you. So on a table where Supabase fills in some columns itself, such as the automatic id, the creation time or the owner, the test can fail. To add or change rows, a function from Nowa AI that only sends the columns you need is often simpler (see the next section).

Ask Nowa AI for other queries​

Templates cover the basics. For anything else, such as a filtered list, a sorted feed, or several queries at once, ask Nowa AI in Agent mode. Ask for the functions in SupabaseService, so they show up in the panel with the right badge, ready to test. With the Supabase connector on, Nowa AI can also look at your tables. Try:

  • "Add a function to SupabaseService that adds a todo with a title for the signed-in user."
  • "Create functions to update a todo's title, mark a todo as done, and delete a todo."
  • "Change getAllTodos so it only returns todos that aren't done, newest first."
  • With the connector on, both steps at once: "Create a todos table where each user only sees their own rows, then add a function that lists them."

Then test each new function, as the next section shows.

Test a function​

  1. Click a function in the Supabase panel. A panel opens at the bottom of the editor, titled with the function's name, for example Testing getAllTodos.
  2. Under Testing values, fill in one field for each input of the function. A function without inputs shows "No parameters to test". For a function that needs a row's ID, copy it from your table in Supabase's Table Editor.
  3. Click Run to run the function on your real project. Until then the result area says "Run test to see result".

The bottom panel titled Testing getAllTodos (highlighted) after Run: on the left the result, a list of 3 items, each an instance of TodosModel with id, created_at, title (Buy milk, Write docs, Walk the dog) and done. On the right, Testing values reads No parameters to test, above the Run button (highlighted) and Edit Code.

Ran a function that adds, changes or deletes rows? Open the table in Supabase's Table Editor (⋮ → Open Supabase) to see the change for yourself.

What the result area can show:

You seeWhat it means
The resultThe data your function returned.
Error: and a messageSupabase or the function returned an error.
RLS Policy ErrorSupabase refused the query. Usually a Row Level Security policy doesn't allow it; on newer projects the table may also not be shared with your app yet (see Create a table). Follow the steps shown (today's Supabase dashboard lists policies under Database → Policies), or click Open Supabase Dashboard. Sign in first if the policy needs a user.
Empty Result - Possible RLS FilteringThe query returned an empty list. If the table has rows, RLS may be hiding them: click Check RLS Policies. If the table is empty, you can ignore it.
Streaming over a colored borderA live query. See Live queries.
warning

Tests run against your real Supabase project. Running a create, update or delete function changes real data.

Change a function's code​

The easiest way to change a function is to ask Nowa AI, as in Ask Nowa AI for other queries. If you edit code yourself, click Edit Code in the test panel:

  1. Change the code in Query Source Code. Discard and Save appear once you change something.
  2. Click Save.
  3. Click Test Function to go back to the test view and run it.

If you rename the function in the code, the new name replaces the old function. If Nowa can't find the function's name in your code, it shows "Could not parse function name from the code".

Live queries​

A live query keeps your screen up to date: when a row changes in Supabase, your app shows the change without a refresh, which is handy for a shared shopping list or a chat. It's a function that returns a Stream (data that keeps updating), and it shows a STREAM badge under Queries. No template creates one.

  1. Turn on Realtime for the table in Supabase, because new tables start with it off. Open your project's Publications settings (Database → Publications), and under supabase_realtime, turn on your table.
  2. Ask Nowa AI in Agent mode, for example: "Add a function that streams my todos, so the list updates live." If you edit code yourself, see What Nowa adds to your code.
  3. Click the function and click Run. The panel shows Streaming and updates as rows change. It keeps listening until you run it again or close the panel.

RPC and other functions​

Two sections fill up from code, not from templates:

  • RPC lists functions that run a database function, a small program stored in your Supabase database, for example one that totals an order.
  • Other Functions lists functions that call a Supabase Edge Function, a small program that runs on Supabase's servers, for example one that sends a welcome email.

Ask Nowa AI to create them with the Supabase connector. You test them and use them in your app like any other function.

Show the results in your app​

A Data Builder shows a function's result in your UI, for example your to-dos in a list. The steps below are the Supabase part. See Show data in your UI for the rest.

  1. Select the widget that should show the rows, often a List View. Click Add Wrapper and choose Data Builder.
  2. Set Source to Supabase. Next to Query, click the button (it reads none at first) and pick your function in Select Supabase Functions. Fill in its inputs, if it has any.
  3. Inside the builder, link widgets to data, which holds what the function returned. For getAllTodos, data is a list of TodosModel.

On the board, you see placeholder values built from your model. To see real data, click Play on the screen, or run the app. See Play your app on the board. The function must return a Future (data that arrives once, a moment later) or a Stream (data that keeps updating).

To change data, call the function from an event, such as a button's On Pressed. Sign users in with Supabase shows the same steps with signIn.

To open a detail screen when someone taps a row, load that row with Get Record by ID. See Open a detail screen when a list item is tapped.

Next steps​