Skip to main content
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

interface JobDefinition {
  id?: string;
  name?: string;
  description?: string;
  variables?: WorkflowVariables;
  steps: StepDefinition[];
}
FieldDescription
nameHuman-readable workflow name
descriptionWhat the workflow does
variablesDynamic parameters resolved at submission time
stepsArray 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.
// 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:
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 and step modifier pages for details.