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

# Defining workflows

> Workflow structure, the JobDefinition DSL, and how steps form a dependency graph

Workflows are defined as TypeScript objects conforming to the `JobDefinition` interface. They live in `src/server/workflows/` and are exported from `src/server/workflows/index.ts`. The app framework's build pipeline validates workflow definitions at build time.

## JobDefinition

```typescript theme={null}
interface JobDefinition {
  id?: string;
  name?: string;
  description?: string;
  variables?: WorkflowVariables;
  steps: StepDefinition[];
}
```

| Field         | Description                                           |
| ------------- | ----------------------------------------------------- |
| `name`        | Human-readable workflow name                          |
| `description` | What the workflow does                                |
| `variables`   | Dynamic parameters resolved at submission time        |
| `steps`       | Array of step definitions that form the execution DAG |

## Example workflow

This workflow syncs Shopify orders enriched with CRM customer data into the app's database. It fetches orders and customers from separate services, joins them with a SQL transformation, and upserts the enriched results.

```typescript theme={null}
// src/server/workflows/syncEnrichedOrders.ts
import type { JobDefinition } from '@synthetiq/workflows';

export const syncEnrichedOrders: JobDefinition = {
  name: "Sync Enriched Orders",
  description: "Fetch Shopify orders, enrich with CRM customer data, and persist",
  variables: {
    orgId: { type: "param" },
    startDate: { type: "dateOffset", days: -30 },
  },
  steps: [
    {
      service: "shopify",
      operation: "listOrders",
      params: { status: "any", created_at_min: "{{var.startDate}}" },
      outputTable: "raw_orders",
      outputSchema: {
        id: "VARCHAR",
        customer_id: "VARCHAR",
        total_price: "VARCHAR",
        created_at: "VARCHAR",
      },
      paginate: {
        type: "cursor",
        cursorField: "nextPageCursor",
        cursorParam: "page_info",
      },
    },
    {
      service: "crm",
      operation: "listCustomers",
      params: { orgId: "{{var.orgId}}" },
      outputTable: "customers",
      outputSchema: {
        id: "VARCHAR",
        name: "VARCHAR",
        email: "VARCHAR",
        segment: "VARCHAR",
      },
    },
    {
      sql: `
        SELECT
          o.id AS order_id,
          o.total_price,
          o.created_at,
          c.name AS customer_name,
          c.email AS customer_email,
          c.segment AS customer_segment
        FROM raw_orders o
        JOIN customers c ON o.customer_id = c.id
      `,
      outputTable: "enriched_orders",
      outputSchema: {
        order_id: "VARCHAR",
        total_price: "VARCHAR",
        created_at: "VARCHAR",
        customer_name: "VARCHAR",
        customer_email: "VARCHAR",
        customer_segment: "VARCHAR",
      },
    },
    {
      persistToDatabase: {
        appId: "{{var.appId}}",
        tableName: "EnrichedOrder",
        sourceTable: "enriched_orders",
        mode: "upsert",
        upsertKey: ["order_id"],
      },
    },
  ],
};
```

## Exporting workflows

Export all workflow definitions from `src/server/workflows/index.ts`:

```typescript theme={null}
export { syncProducts } from './syncProducts';
export { refreshMetrics } from './refreshMetrics';
```

## Dependency graph

Steps execute based on their data dependencies. A step that references a table produced by another step waits for that step to complete first. Independent steps can run in parallel.

In the example above:

1. The Shopify step produces `raw_orders` and the CRM step produces `customers` — these have no shared dependencies, so they run in parallel
2. The SQL step reads both `raw_orders` and `customers` — so it waits for both to complete
3. The persist step reads `enriched_orders` — so it depends on the SQL step

## Build-time and submit-time validation

Workflows are validated at two stages:

* **Build time** — the app framework's build pipeline validates workflow structure, table references, SQL syntax, service schemas, database schemas, and variable references
* **Submit time** — validation runs again with resolved parameter values. Jobs with a matching active idempotency key are rejected.

Specific validations depend on the step types and step modifiers being used — see individual [step type](/docs/platform-docs/workflows-framework/steps/import-from-db) and [step modifier](/docs/platform-docs/workflows-framework/pagination) pages for details.
