Building with it

Authoring workflows

A workflow is a published flow: a project key, a title, and an ordered list of steps. You create one by posting it to the backend, and from then on the SDK can serve it by key or match it to a user's goal.

The shape of a flow

Each step names an action, a selector, and the words the user reads.

one step
{
  action: "click",                       // click | input | navigate | submit
  selector: "[data-nudge-id=\"save\"]", // an element in your app
  fallback_selectors: ["button:has-text('Save')"],
  instruction: "Click Save to publish the invoice",
  why_text: "This makes the invoice visible to the customer"  // optional
}

Publishing a flow

Send the flow to POST /api/admin/flows. This is an admin route, so it needs a secret key (sk_) that carries the authoring capability, not a publishable key.

bash
curl -X POST https://your-backend.com/api/admin/flows \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "project_key": "create-invoice",
    "title": "Create an invoice",
    "steps": [
      {"action":"click","selector":"[data-nudge-id=\"new-invoice\"]","instruction":"Click New invoice","why_text":"Opens the editor"},
      {"action":"input","selector":"[name=\"customer\"]","instruction":"Type the customer name"},
      {"action":"click","selector":"[data-nudge-id=\"save\"]","instruction":"Click Save","why_text":"Publishes it"}
    ]
  }'

Now defaultProjectKey: "create-invoice" plays it, or in programmatic mode you call nudge.start({ projectKey: "create-invoice" }).

Versions

Posting the same project_key again appends a new version and publishes it. The old version stays in history, so a flow is never overwritten in place. That is what makes a safe edit-and-republish loop, and it is the basis for the drift and selector-promotion machinery on the backend.

Choosing selectors that last

The selector is the part most likely to break when your app changes. In order of preference:

  1. data-nudge-id attributes you add on purpose. These survive redesigns because they exist only for this.
  2. data-testid if you already have them from your test suite.
  3. id or name attributes when they are stable.
  4. Structural selectors like .btn:nth-child(3) only as a last resort. They break the moment the layout shifts.

Set fallback_selectors too. If the primary selector stops matching at runtime, the SDK tries the fallbacks in order, and reports the miss so you know the flow needs attention.

Three ways to author

Hand-posting JSON is the direct route. You can also record a flow by clicking through your app (see Dev mode and capture), or let the backend generate one from your vocabulary. All three end up as a published flow in the same table.