Thumbnail image for changelog item


We released pricing for Dopt. We’re charging based on monthly tracked users (MTUs). MTUs are the number of unique users that our blocks SDKs have been initialized with in a given month (i.e. users that might qualify for a flow, even if they don’t end up qualifying for any). If you’re sending Dopt all of your users, MTUs will typically map to the number of monthly unique active users in your product.

Pricing tiers

We picked MTUs because it’s a metric that’s simple and easy to understand and predict. It also maps to the value your organization will gain from using Dopt to more rapidly build experiences that help onboard and educate your users.

You can see our pricing page for more details.

Editing block identifiers

We’ve added the ability to edit block identifiers which now makes encoding semantics into the identifier possible. When a block is created, we will automatically generate an identifier for you. Block identifiers can be edited in the uncommitted version by using the Rename action from the panel menu (or just click the identifier in the panel). We’ve also made it easier to see which block maps to an identifier by displaying the identifier on the canvas.

Block identifier

Block identifiers must be unique to a flow so we’ve updated how you access blocks via SDK which should now use a format of $flowId.$blockId. So if you had a flow with an identifier new-user-tour and a block inside of that flow with an identifier welcome-intro, then you would access that block using new-user-tour.welcome-intro.

Don’t worry, we’ve also kept the previous method intact as well which we’re now calling uid. Accessing a block using its uid does not require the flow identifier to be prefixed. uids are generated for you, cannot be changed, and are unique across all of your flows. They’ll look something like V1StGXR8_Z5jdHi6B-myT. You can grab a block’s uid using the Copy UID action from the panel menu. Typically, you will use the uid when accessing a block directly from the API.

Other improvements

  • Improved the usability of the copy to clipboard action
  • Implemented caching of common entities for performance improvements to our public APIs

Thumbnail image for changelog item

Webhook block

We’re introducing a new block called the webhook block that allows you to send data to an external API. You can also insert data about the flow, user, and request itself into the webhook’s request body to further contextualize the data you send.

Flow with webhook block

The webhook block can be incredibly useful for tasks like triggering a follow up email or notifying your team in an internal Slack channel after one of your users has completed a step in a flow.

Read the webhook block docs →

Interactive open-source examples

Other improvements and bug fixes

  • Reorganized docs for block types to make them easier to find
  • Exposed loading methods and initialized statuses in our React and JS SDKs. You can use useDoptInitalized() to understand when Dopt’s overall initialization is complete and useFlowStatus() to understand the status of flows.
  • Removed the ability to mock flows and blocks in the react SDK. Instead, we suggest testing by identifying test users, using your development environment, and working against uncommitted versions.
  • You can now load your uncommitted flows via the Dopt SDK by specifying a version number 0. This enables you to rapidly iterate on your flows without having to commit versions before you’re ready.
  • We’ve refactored our React and JS SDKs to no longer expose internal promises when intentions like complete are called (React, JS). These methods result in server-side side effects which will eventually cause reactive state changes via client-side states; consumers can rely on our hooks in React and our subscriptions in JS to understand when state changes without relying on intention resolution.
  • We’ve deprecated older, unused user identification APIs.
    • We continue to support but have deprecated the /identify/{:identifier:} API. This API specified the user’s identifier in the URL — our current API, /identify specifies the user’s identifier in the request body along with the rest of the user’s properties; we’ve found this change has led to fewer unintentional errors when consuming the API.
    • We’ve removed all support for the /identify/bulk API in favor of our current /identify/batch API.

Thumbnail image for changelog item

Content fields

Fields enable you to define content in Dopt and access it with our SDKs to power in-product copy and configuration. You can think of fields like a lightweight in-product CMS — paired with the rest of our platform’s powerful building blocks to define the user targeting, logic, and actions of your onboarding and education flows.

Fields are key-value pairs that you can define as part of a block in Dopt’s visual flow builder. You can specify the type of the field: text, boolean, and number. We’ll be releasing more field types in the future.

Here’s a flow to power a simple modal:

Step block with fields

These fields can then be accessed with Dopt’s SDKs to power your product experience.

import { useBlock } from '@dopt/react';import { Modal } from './modal';
export function Application() {  const [{ state, getField }, { complete }] = useBlock('$BLOCK_ID');  return (    <main>      { && (        <Modal>          <h1>{getField('title')}</h1>          <p>{getField('body')}</p>          <button onClick={complete}>{getField('button')}</button>        </Modal>      )}    </main>  );}

Read the full blog post →

Read the fields docs →

1.0 SDK

The 1.0 release represents a significant milestone in terms of stability and our confidence in the core solution. It includes:

  • A reorganization of the block and flow states we expose
  • Methods for accessing states and commands for changing states
  • A new representation of the block and flow entities that we return to you
  • Group block types

Here’s the updated useBlock hook type definition:

interface Block<T> {  readonly kind: 'block';  readonly type: T;  readonly uid: string;  readonly sid: string;  readonly version: number;  readonly state: {    active: boolean;    completed: boolean;  };}
interface BlockIntention {  complete: () => void;}
const useBlock: (uid: Block['uid']) => [block: Block, intent: BlockIntention];

We offer a useFlow hook as the primary mechanism for accessing and transitioning flows. Here’s the hooks type definition:

interface Flow<T> {  readonly kind: 'flow';  readonly type: T;  readonly uid: string;  readonly sid: string;  readonly version: number;  readonly state: {    started: boolean;    completed: boolean;    exited: boolean;  };  readonly blocks: Block[];}
interface FlowIntention {  complete: () => void;  reset: () => void;  start: () => void;  exit: () => void;}
const useFlow: (uid: Flow['sid']) => [flow: Flow, intent: FlowIntention];

The most powerful concept here is that the flow has references to its blocks — meaning that you can use this hook to access all state for a flow (its state and the state of its blocks) and get live updates as that state changes.

You might have noted in the type definitions above that the type property on the flow and block interfaces is generic — this is to support us building and exposing new block types.

Read the full blog post →

Read the SDK docs →

Blocks JS client

We released a blocks JavaScript client. The blocks client is simple, language specific client to interface with Dopt’s blocks API for both client-side and server-side integrations.

Read the Blocks JS client docs →

Other improvements and bug fixes

  • Flow states on the users page now updates automatically.
  • Added “when all complete” to group block to make the logic more clear on the canvas.
  • Updated flow panel so values are automatically saved.
  • Fixed a bug when flow listing page wouldn’t load with an archived flow. Archived flows now will not be removed from users page.

Thumbnail image for changelog item

User groups, demo, & examples

Introducing user groups

We’re excited to introduce user groups for Dopt. A group is usually a company or a workspace, but could also be an account, project, team, or any other user grouping that is relevant to your product.

B2B SaaS products are collaborative by default. Users collaborate on workflows and complete tasks together, usually within a company, workspace, or account. Groups enable you to target Dopt onboarding and education flows based on the properties of those groups, not just an individual user.

Groups enable use cases like:

  • If == "pro", show the Pro onboarding flow that highlights the unique value props in that plan.
  • If == false AND == "admin", then show those admin users a flow to help them set up the integration.
  • If < 3, then show a flow to help users create their first projects so the company can work towards activating.

You set the targeting rules in the Start block.

Flow with group defined in targeting rule

Groups also give you an overview of users and their activities within a company, helping you understand how they’re experiencing your flows.

Group details page

Users can belong to many groups, supporting products where a user can be in many workspaces (like Notion or Slack).

You can associate users to groups when identifying users. This also acts as an upsert for the groups and group properties — if the group doesn’t exist, it will get created with the set properties, if the group does exist, the group properties will get updated. Here’s an example:

curl \-XPOST \-H "Content-Type: application/json" \-H "x-api-key:$USERS_API_KEY" \-d '{      "identifier": "2a845972-4cde-4cb4-ba14-5cb2fc15ec4c",      "properties": {        "name": "Evelyn Reichert",        "email": "",      },      "groups": [        {          "identifier": "21ab4-8786ca4-78e63c-4525ca434",          "properties": {            "name": "Acme co",            "plan": "pro",            "integration_setup": false,            "num_projects": 3,          }        }      ]    }'

Read the user group docs →

Demo and examples

We released a 5 min getting started demo that covers the basics of building onboarding with Dopt, from using the flow builder to using our SDKs to develop the experience.

We also released two example onboarding experiences built with Dopt:

  • Learn-by-doing onboarding example: This example shows how to use Dopt to build onboarding for a Kanban app that helps users learn the product by simply using it.
  • Getting started checklist example: This example shows a checklist style onboarding experience that guides users through the three key actions to get value out of a simple analytics app.

Stay tuned, we have got more coming!

Other improvements & fixes

  • You can keep your flow listing page tidy by archiving flows. When a flow is archived, it’s removed from the flow listing page and its configuration, versions, and associated user states are also removed. Archive flow docs →
  • Moved our Typedoc docs for the React SDK to