# How pgflow Works

import { Aside, CardGrid, LinkCard } from "@astrojs/starlight/components";
import SVGDAGAnimation from "../../../components/SVGDAGAnimation.astro";
import JoinCommunity from "../../../components/JoinCommunity.astro";

pgflow runs **multi-step workflows** (flows) on Supabase with automatic **parallelization**, **retries**, and **dependency tracking**. This page shows how it works.

<div class="animation-explainer">
  <div class="explainer-text">

This flow demonstrates three key behaviors:

**🔀 Parallel execution** - `summarize` and `extractKeywords` start immediately after `fetchArticle` completes.

**🔄 Automatic retry** - When `summarize` fails, pgflow retries automatically.

**🔗 Dependency coordination** - `publish` waits for both steps to complete.

  </div>

  <div class="explainer-animation">
    <SVGDAGAnimation showCaption={false} large={true} />
  </div>
</div>

<style>{`
  .animation-explainer {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem;
    align-items: start;
  }

  .explainer-animation {
    position: sticky;
    top: 2rem;
    margin-top: -1rem;
  }

  @media (max-width: 1024px) {
    .animation-explainer {
      grid-template-columns: 1fr;
      gap: 2rem;
    }

    .explainer-text {
      text-align: left;
    }

    .explainer-animation {
      position: relative;
      top: 0;
      max-width: 100%;
      width: 100%;
      margin: 0;
      overflow: hidden;
    }
  }
`}</style>

---

Now let's understand what happens under the hood.

## How pgflow Makes This Possible

At its core, pgflow does two things:

1. 🧠 **Figures out** which steps are ready based on dependencies
2. 📤 **Pushes tasks** into queues for workers to execute

Workers pull from these queues and run your functions.

<Aside type="note" title="Key Separation">
**pgflow** handles orchestration (what to run, when)

**You** handle business logic (how to process data)
</Aside>

---

## From Code to Execution

🔨 **Build-time**: Write workflows in TypeScript, compile to SQL migrations

⚡ **Run-time**: Postgres orchestrates (determines what runs when), workers execute your functions

All workflow state lives in Postgres tables - giving you full observability.

---

## Defining the Workflow

Workflows are built by chaining `.step()` calls. Each step has a unique **slug** (identifier) and declares which steps it **dependsOn**:

```typescript
new Flow<Input>({ slug: "process_article" })
  .step({ slug: "fetchArticle" }, fn)
  .step({ slug: "summarize", dependsOn: ["fetchArticle"] }, fn)
  .step({ slug: "extractKeywords", dependsOn: ["fetchArticle"] }, fn)
  .step({ slug: "publish", dependsOn: ["summarize", "extractKeywords"] }, fn);
```

pgflow uses dependencies to figure out execution order and parallelism. Steps with the same dependencies run in parallel. Return values become available as `input.<slug>` to dependent steps.

<details>
<summary>See full code</summary>

```typescript "input.run" "dependsOn: [\"fetchArticle\"]" "input.fetchArticle" "dependsOn: [\"summarize\", \"extractKeywords\"]" "input.summarize" "input.extractKeywords"
type Input = { url: string };

new Flow<Input>({ slug: "process_article" })
  .step(
    { slug: "fetchArticle" },
    async (input) => {
      const response = await fetch(input.run.url);
      return response.text();
    }
  )
  .step(
    { slug: "summarize", dependsOn: ["fetchArticle"] },
    async (input) => {
      return await openai.summarize(input.fetchArticle);
    }
  )
  .step(
    { slug: "extractKeywords", dependsOn: ["fetchArticle"] },
    async (input) => {
      return await openai.extractKeywords(input.fetchArticle);
    }
  )
  .step(
    { slug: "publish", dependsOn: ["summarize", "extractKeywords"] },
    async (input) => {
      await db.insert({
        summary: input.summarize,
        keywords: input.extractKeywords
      });
    }
  );
```

</details>

## Deploy as Edge Function

No **Deno.serve** boilerplate - three lines sets up a [persistent](/reference/queue-worker/how-it-works/) worker:

```typescript
import { EdgeWorker } from "jsr:@pgflow/edge-worker";
import ProcessArticle from './process_article.ts';

EdgeWorker.start(ProcessArticle);
```

<Aside type="note" title="Continuous Workers">
pgflow workers use Supabase Background Tasks and add automatic respawning, continuous polling, and heartbeats for ease of use, continuity, observability, and \<100ms task startup. [How it works →](/reference/queue-worker/how-it-works/)
</Aside>

## Start Your Flow

Trigger a run from SQL:

```sql
SELECT pgflow.start_flow(
  flow_slug => 'process_article',
  input => '{"url": "https://example.com"}'::jsonb
);
```

Since it's a SQL function, call it anywhere: schedule with `pg_cron`, use in db triggers, within transactions, from your app via Supabase RPC, or use the pgflow client for realtime updates.

---

That's it - dependencies determine execution order, pgflow handles parallelism and retries, all state lives in Postgres.

## Learn More

<CardGrid>
  <LinkCard
    title="Install pgflow"
    href="/get-started/installation/"
    description="Set up pgflow in your Supabase project with one command"
  />
  <LinkCard
    title="Create your first flow"
    href="/get-started/flows/create-flow/"
    description="Build a simple workflow to see how steps connect"
  />
  <LinkCard
    title="Monitor Execution"
    href="/deploy/monitor-execution/"
    description="Query step states, view execution timeline, troubleshoot failures"
  />
  <LinkCard
    title="Three-layer architecture"
    href="/concepts/three-layer-architecture/"
    description="Deep dive into three-layer separation and design philosophy"
  />
</CardGrid>

<JoinCommunity />