> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noxus.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tables in Flows

> Read, write, query and export tables with flow nodes

export const NodeSection = ({title, afterTable = false}) => <p className="not-prose text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400" style={{
  marginTop: afterTable ? "12px" : "28px",
  marginBottom: "6px"
}}>
    {title}
  </p>;

export const NodeCardGroup = ({children}) => {
  const FADE_MS = 150;
  const [active, setActive] = useState(0);
  const [shown, setShown] = useState(0);
  const [visible, setVisible] = useState(true);
  const timer = useRef(null);
  useEffect(() => () => clearTimeout(timer.current), []);
  const unwrap = node => {
    if (!node || !node.props) return null;
    if (node.props.title) return node;
    return [].concat(node.props.children).map(unwrap).find(Boolean) || null;
  };
  const cards = [].concat(children).map(unwrap).filter(Boolean);
  const select = index => {
    if (index === active) return;
    clearTimeout(timer.current);
    setActive(index);
    setVisible(false);
    timer.current = setTimeout(() => {
      setShown(index);
      setVisible(true);
    }, FADE_MS);
  };
  const GLYPHS = {
    get: <path d="M18 14v7m-3-3 3 3 3-3" />,
    insert: <path d="M18 14v7m-3.5-3.5h7" />,
    update: <path d="m15 21 .5-2 4-4 1.5 1.5-4 4z" />,
    delete: <path d="m15 15 6 6m0-6-6 6" />,
    query: <><circle cx="17.5" cy="17.5" r="2.5" /><path d="m21 21-1.5-1.5" /></>,
    export: <path d="M18 21v-7m-3 3 3-3 3 3" />
  };
  const nodePreview = card => {
    const {title, glyph, fields = [], block} = card.props;
    return <div className="not-prose relative bg-zinc-50 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800" style={{
      padding: "28px 16px",
      display: "flex",
      justifyContent: "center"
    }}>
        <div aria-hidden="true" className="text-zinc-300 dark:text-zinc-700" style={{
      position: "absolute",
      inset: 0,
      backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)",
      backgroundSize: "14px 14px"
    }} />
        <div className="relative" style={{
      width: "100%",
      maxWidth: "300px"
    }}>
          {["left", "right"].map(side => <span key={side} className="bg-white dark:bg-zinc-900 border-2 border-zinc-300 dark:border-zinc-600" style={{
      position: "absolute",
      top: "22px",
      [side]: "-26px",
      width: "16px",
      height: "16px",
      borderRadius: "5px"
    }} />)}
          <div className="rounded-xl border-2 border-blue-100 dark:border-blue-900/60 bg-white dark:bg-zinc-950 shadow-sm overflow-hidden">
            <div className="bg-blue-50 dark:bg-blue-950/40 flex items-center gap-3 px-4 py-3">
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-blue-700 dark:text-blue-400 shrink-0">
                <path d="M12 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v7" />
                <path d="M3 9h18M9 3v18" />
                {GLYPHS[glyph]}
              </svg>
              <span className="flex-1 text-[15px] font-medium text-zinc-900 dark:text-zinc-100">{title}</span>
              <span className="text-blue-700 dark:text-blue-400" style={{
      fontWeight: 700,
      letterSpacing: "-1px"
    }}>⋮</span>
            </div>
            <div className="bg-zinc-50 dark:bg-zinc-900 px-4 py-2 text-[13px]">
              {fields.map(([label, value]) => <div key={label} className="flex justify-between gap-4 py-1.5">
                  <span className="text-zinc-500 dark:text-zinc-400">{label}</span>
                  <span className="text-zinc-900 dark:text-zinc-100">{value}</span>
                </div>)}
              {block && <div className="py-1.5">
                  <div className="text-zinc-500 dark:text-zinc-400 mb-1.5">{block[0]}</div>
                  <div className="rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-950 px-3 py-2 font-mono text-[12px] text-zinc-900 dark:text-zinc-100">
                    {block[1]}
                  </div>
                </div>}
            </div>
          </div>
        </div>
      </div>;
  };
  const card = cards[shown];
  return <div className="rounded-2xl border border-zinc-200 dark:border-zinc-800 overflow-hidden flex flex-col sm:flex-row">
      <div className="not-prose flex sm:flex-col gap-1 p-2 overflow-x-auto sm:w-44 shrink-0 border-b sm:border-b-0 sm:border-r border-zinc-200 dark:border-zinc-800">
        {cards.map((c, index) => <button key={index} type="button" onClick={() => select(index)} aria-pressed={active === index} className={"text-left whitespace-nowrap rounded-lg px-3 py-2 text-sm cursor-pointer transition-colors " + (active === index ? "bg-primary/10 text-primary dark:text-primary-light font-semibold" : "text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800/60")}>
            {c.props.title}
          </button>)}
      </div>
      <div className="flex-1 min-w-0" style={{
    opacity: visible ? 1 : 0,
    transition: `opacity ${FADE_MS}ms ease`
  }}>
        {nodePreview(card)}
        <div className="px-5 pt-5 pb-4" style={{
    overflowX: "auto"
  }}>
          <p className="not-prose text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400" style={{
    marginTop: 0,
    marginBottom: "6px"
  }}>
            What it does
          </p>
          {card.props.children}
        </div>
      </div>
    </div>;
};

export const NodeCard = ({children}) => <>{children}</>;

<Frame>
  <video autoPlay muted loop playsInline controls preload="auto" width="3840" height="2104" style={{ width: "100%", height: "auto" }}>
    <source src="https://storage.googleapis.com/noxus-docs-videos/tables/tables-flows-v4.mp4" type="video/mp4" />
  </video>
</Frame>

## Table nodes

Every node except Query Table has a **Table** field where you pick one of the workspace's tables. The table can't be bound to a variable. Pick a node to see its fields and outputs.

<NodeCardGroup>
  <NodeCard title="Get Table Rows" description="Reads rows from a table." glyph="get" fields={[["Table", "customers"]]}>
    Reads rows from a table.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **Max rows** | 50 | Values below 1 fall back to 50. Hidden when **Return only first row** is on. |
    | **Return only first row** | Off | Returns one row as an object instead of a list. |
    | **Filters** | Empty | One entry per column. Only rows where every filled-in column equals its value are returned. Leave a column blank to ignore it. |
    | **Order by** | None | Under **Advanced options**. The `id` column or any column. |
    | **Descending** | Off | Under **Advanced options**. |

    <NodeSection title="Outputs" afterTable />

    The matching **Rows** (or a single **Row** when **Return only first row** is on, skipped if nothing matches) and a **Row Count** of how many rows it returned.
  </NodeCard>

  <NodeCard title="Insert Table Row" description="Inserts one row." glyph="insert" fields={[["Table", "customers"]]}>
    Inserts one row.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **Values** | Empty | One entry per column. Type a value or bind a variable. |

    <NodeSection title="Outputs" afterTable />

    The inserted **Row**, including its new `id`.
  </NodeCard>

  <NodeCard title="Update Table Row" description="Updates every row that matches the filters." glyph="update" fields={[["Table", "customers"]]}>
    Updates every row that matches the filters.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **Match rows where** | Empty | One entry per column, plus `id`. Every filled-in column must equal its value. At least one is required. |
    | **Set values** | Empty | The new values. Only filled-in columns are written. At least one is required. |

    <NodeSection title="Outputs" afterTable />

    An **Updated Count** of how many rows changed.

    <Note>
      Values are passed as data, never as SQL, so this is the safe way to change rows with variables.
    </Note>
  </NodeCard>

  <NodeCard title="Delete Table Row" description="Deletes every row that matches the filters." glyph="delete" fields={[["Table", "customers"]]}>
    Deletes every row that matches the filters.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **Match rows where** | Empty | One entry per column, plus `id`. Every filled-in column must equal its value. At least one is required. |

    <NodeSection title="Outputs" afterTable />

    A **Deleted Count** of how many rows were deleted.

    <Note>
      Values are passed as data, never as SQL.
    </Note>
  </NodeCard>

  <NodeCard title="Query Table" description="Runs SQL over the workspace's tables." glyph="query" block={["SQL query", "SELECT * FROM customers"]}>
    Runs SQL over the workspace's tables.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **SQL query** | Empty | Required. Can include variables. An AI button can write the query for you. |
    | **Return only first row** | Off | Returns the first row as an object instead of a list. |
    | **Allow an INSERT statement** | Off | Under **Advanced options**. |
    | **Allow an UPDATE statement** | Off | Under **Advanced options**. |
    | **Allow a DELETE statement** | Off | Under **Advanced options**. |

    <NodeSection title="Outputs" afterTable />

    The result **Rows** (or a single **Row** when **Return only first row** is on, empty for a write) and a **Row Count** of rows read or changed.

    <Note>
      Reads by default; writes need a toggle. See [SQL Queries](/platform/tables/using/sql).
    </Note>

    <Warning>
      For values from users, emails or other external sources, use **Insert**, **Update** or **Delete Table Row** nodes rather than variables in **Query Table** SQL.
    </Warning>
  </NodeCard>

  <NodeCard title="Export Table" description="Exports rows as a file." glyph="export" fields={[["Table", "customers"], ["Format", "PDF"]]}>
    Exports rows as a file.

    <NodeSection title="Configuration" />

    | Field | Default | Notes |
    | :- | :- | :- |
    | **Format** | CSV | CSV, Excel (XLSX), JSON, PDF or HTML. |
    | **File name** | Table name | Without the extension. |
    | **Columns** | All columns | Select all, then untick columns to leave them out. |
    | **Filters** | None | Pick a column, an operator and a value. Rows must match every condition. Reference columns can't be filtered on. |

    <NodeSection title="Outputs" afterTable />

    The exported **File** and a **Row Count** of how many rows it contains.
  </NodeCard>
</NodeCardGroup>

## Choosing a node

| You want to | Use |
| :- | :- |
| Read rows by simple filters | **Get Table Rows** |
| Add one row | **Insert Table Row** |
| Change or delete rows that match filters | **Update Table Row** or **Delete Table Row** |
| Join tables, aggregate, or write custom SQL | **Query Table** |
| Produce a CSV, Excel, JSON, PDF or HTML file | **Export Table** |

## Exporting and importing flows

When you export a flow with **Include dependencies** on, the tables its nodes use are bundled into the export with their columns and rows, along with any tables they reference. Importing the flow recreates them.

* A table's rows can be up to 15 MB in the export. If a table is bigger, the export is refused.
* On import, each table keeps its identifier when it is free in the target workspace. If the identifier is taken, a suffix such as `_1` is added.
* **Query Table** SQL isn't rewritten on import. If a table's identifier changed, update the SQL.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.