At a glance
- Takes
- Any payload — its variables become the values you store
- Emits
- inserted, rowId, rowsAffected
- Good for
- Giving a flow a memory with no schema step
Set it up
Nothing has to exist first. Pick a database (or create one from the picker), type a table name that doesn't exist yet, and list the fields to store — each one a column name you invent and a value, either a literal or a {{variable}} from an earlier node. The first run creates the table, its columns and the row together.

Configure
| Field | Default | What it does |
|---|---|---|
| Database | — | Which database to write to. The picker fills in the id. |
| Table | — | The table to write to. It does not have to exist yet. |
| Fields | — | Each row is a column and the value to store in it. |
| Create the table if it doesn't exist | On | Turn it off once a table matters, so a typo in Table can't quietly start a second one. |
| On a conflicting row | Stop with an error | Skip the row, or Replace the row, when the write collides with a unique index you added yourself. |
A field's value is read one of two ways. A lone {{text}} keeps the upstream value's real type — a number stays a number. Anything else is a literal: 42 is an integer, 3.5 a real, true the integer 1, and "00123" in quotes is text, which is how a serial number keeps its leading zero. A mixed template like {{first}} {{last}} renders to text.
Output
Every key is available downstream as {{key}}.
| Key | Type | Value |
|---|---|---|
inserted | Boolean | true when a row was written; false when a conflict rule skipped it |
rowId | Number | The managed id of the row just written, or null when nothing was. Store it to come back to that exact row |
rowsAffected | Number | How many rows were written. 0 is a normal answer when a conflict rule skipped the row |
Example: store a note and confirm it
Input hands the flow some text. Save to Database writes it into a clips table that it creates on the first run. Notification reports the id it got back.

Paste this into the Flow Builder to get the same flow:
Take text from an Input trigger, save it to a clips table in my Clipboard History database, and notify me with the new row id.
Good to know
id and created_at are reserved. Every table this node makes gets both — an auto-numbering primary key and a UTC ISO-8601 stamp. A field with either name is refused; call it item_id instead.
A field that doesn't resolve is skipped, not blanked. If a value is exactly {{something}} and that variable isn't in the input, no column is created and no empty value is stored; the run log names what was skipped. If nothing resolves at all the node fails rather than writing a blank row.
A table auto-grows to 64 of your own columns (the managed pair doesn't count). Past that the node stops and points you at Run SQL, where you can shape a wide table deliberately.
SQLite has no boolean type. true is stored as 1 and reads back as the number 1, so match a flag with equals 1.
Replace the row deletes first. A node set to Replace is badged destructive: the colliding row is removed before the new one is written, and a database has no Trash.