npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blazeo.com/form-builder-client

v0.3.0

Published

Blazeo Forms API client with MobX State Tree models

Readme

@blazeo.com/form-builder-client

JavaScript client for FormBuilderService. Configure it once, then call the service through MobX State Tree models. FormModel covers authoring, TemplateModel the create-form picker, VersionModel publish history, ShareModel the public link and embed, SendModel per-lead sends, SubmissionModel stored responses, PublicFormModel the open renderer, MappingModel draft field mapping, EnrichmentModel pending lead-field changes, and AnalyticsModel the Responses-tab charts. AuthModel pings the service, and OutboxModel delivers one outbox batch. createRootStore() keeps the records the screen has open.

Instance methods that change the open form return the service envelope (status, message, data, errors) plus _httpStatus, and on success write the camelCase detail onto the node. FormModel.list, FormModel.get, FormModel.createForm, FormModel.importForm, and duplicate return a node (or a page of nodes) on success. A failure is that envelope.

Use it locally

From this folder:

npm install
npm run build

From a UI app:

npm install ../FormBuilderService/client
import { configure, createToken, setAccessToken, setConsumer, FormStatus } from "@blazeo.com/form-builder-client";

configure({
  baseUrl: "https://localhost:7125",
});

await createToken({ apiKey: "key", apiSecret: "secret" });
// or, when the UI already has a bearer token:
setAccessToken(existingJwt, "2026-10-13T22:00:00Z");

setConsumer("smarthub"); // optional Consumer header on authenticated calls

createToken calls POST /Api/Auth/Token without a bearer token, then stores access_token. Later authenticated calls send Authorization: Bearer. setGetAccessToken can supply a replacement when the stored token is missing or within 60 seconds of expires_at_utc.

A call returns a node, or a page of nodes, when it succeeds. A failure is the service envelope (status, message, data, errors) plus _httpStatus. The client does not throw for a failed response. SurveyJS schema and answers, embed config, and lead fields are stored and sent as the service wrote them.

API overview

| Export | Role | | --- | --- | | configure, createToken, setAccessToken, setConsumer, setGetAccessToken | One-time setup and the bearer token | | FormModel | Authoring. Static list, get, createForm, importForm, getLocales, saveLocales, saveCaptcha, editWithAi. Instance get, update, saveSchema, publish, deactivate, activate, archive, duplicate, export, summary, timeseries, getLocales, saveLocales, saveCaptcha, editWithAi | | TemplateModel | Static list, save, archive. Instance archive | | VersionModel | Static list, get. Instance get, restore | | ShareModel | Static and instance getLink, getEmbedConfig, saveEmbedConfig, getSnippet | | SendModel | Static send, listByLead, get. Instance get | | SubmissionModel | Static list, get, listByLead, linkLead, exportCsv, download. Instance get, linkLead, listPending, approve, dismiss, approveAll, dismissAll | | PublicFormModel | Static get, submit, opened, uploadFile, getFile. These skip the bearer token | | WebhookModel | Static and instance get, save, delete | | FileModel | upload and getPublic skip the bearer token. get is the authenticated download | | MappingModel | Static getLeadFields, get, save, setEnabled. Instance get, save, setEnabled | | EnrichmentModel | Static listPending, approve, dismiss, approveAll, dismissAll | | AnalyticsModel | Static summary, timeseries. The same methods on a form return an analytics node | | AuthModel | Static ping. Skips the bearer token | | OutboxModel | Static dispatch | | createRootStore | Holds forms, versions, sends, submissions, and enrichment. Static methods do not need it |

Forms

import { configure, createToken, FormModel, FormType } from "@blazeo.com/form-builder-client";

configure({ baseUrl: "https://localhost:7125" });
await createToken({ apiKey: "key", apiSecret: "secret" });

const form = await FormModel.createForm({
  accountId: "account-id",
  name: "Intake",
  formType: FormType.LeadIntake,
  createdBy: "user-id",
});

await form.saveSchema({ pages: [] });
await form.publish("user-id");

const page = await FormModel.list({ accountId: "account-id", page: 1 });
const copy = await form.duplicate({ createdBy: "user-id", name: "Intake Copy" });

page.forms are list rows. update({ name }) on a row renames it and leaves description and the submission title out of the body until get() has loaded the detail. duplicate returns the new draft and leaves the source node as it was. export returns the envelope; data is the definition document to pass back to importForm.

get() also fills defaultLocale, locales, captchaEnabled, captchaProvider, and captchaSiteKey. List rows leave those at their defaults. getLocales() adds schemaLocales from the draft. saveLocales and saveCaptcha write the returned form detail onto the node. editWithAi(instruction, true) writes the returned schema onto the draft; false returns the schema in the envelope and leaves the node as it was. The CAPTCHA secret stays in service config.

Templates

import { FormModel, TemplateModel } from "@blazeo.com/form-builder-client";

const picker = await TemplateModel.list("account-id");
const contact = picker.categories.find((group) => group.category === "Contact Info");
const template = contact.templates[0];

await FormModel.createForm({
  accountId: "account-id",
  name: template.name,
  formType: template.defaultFormType,
  templateKey: template.templateKey,
  createdBy: "user-id",
});

TemplateModel.list() returns the seeded templates. Pass accountId and the same call also returns that account's custom templates. isCustom is true for those. save copies a form draft into a custom template and returns the node. archive hides a custom template; seeded templates stay in place. Each template node keeps the SurveyJS schema from the service. Pass templateKey to createForm and the service copies that schema into the new draft.

Versions

import { VersionModel } from "@blazeo.com/form-builder-client";

const history = await VersionModel.list("account-id", "form-id");
const previous = history.versions.find((version) => !version.isActive);

await previous.get();
const draft = await previous.restore();

list returns { versions } in the service's order, newest first. A list row has no schema until get(). restore copies that version into the draft and returns the form node. The version node stays as published, and the active version number on the form does not change until the next publish.

Share and embed

import { ShareModel } from "@blazeo.com/form-builder-client";

const share = await ShareModel.getLink("account-id", "form-id");
await share.saveEmbedConfig({ layout: "popup", trigger: { type: "delay", seconds: 5 } });
await share.getSnippet();

getLink, getEmbedConfig, and getSnippet fill their own fields on the same node. embedConfig is sent and stored verbatim. Pass null to saveEmbedConfig to clear it. snippet is the copy-paste markup from the service.

Sends

import { SendModel, SendChannel, SendSource } from "@blazeo.com/form-builder-client";

const send = await SendModel.send({
  accountId: "account-id",
  formId: "form-id",
  leadId: "lead-id",
  channel: SendChannel.Email,
  source: SendSource.LeadDetail,
  sentBy: "user-id",
  leadFields: { email: "[email protected]" },
});

const page = await SendModel.listByLead({ accountId: "account-id", leadId: "lead-id" });
await page.sends[0].get();

send records the per-lead link and returns it on the node as link. Delivery of the email or SMS stays with the caller. leadFields is the lead's current values, kept verbatim for later enrichment. listByLead returns { sends, page, pageSize, totalCount }.

Submissions

import { SubmissionModel } from "@blazeo.com/form-builder-client";

const page = await SubmissionModel.list({ accountId: "account-id", formId: "form-id" });
await page.submissions[0].get();
await page.submissions[0].linkLead("lead-id");

const file = await SubmissionModel.exportCsv({ accountId: "account-id", formId: "form-id" });
const one = await SubmissionModel.download("account-id", "submission-id", "pdf");

list and listByLead return { submissions, page, pageSize, totalCount }. A list row has no answers until get(). get() also fills respondentLocale. linkLead stores the lead id the CRM created and writes the updated detail onto the node. exportCsv and download return the file envelope (FileName, ContentType, Content). download accepts json, csv, or pdf. PDF Content is base64.

Public form

import { PublicFormModel } from "@blazeo.com/form-builder-client";

const form = await PublicFormModel.get("share-token", "fr-CA");
await PublicFormModel.opened("send-token");
const submitted = await PublicFormModel.submit({
  token: "share-token",
  answers: { email: "[email protected]" },
  source: "Link",
  locale: "fr-CA",
  captchaToken: "widget-token",
});
const stored = await PublicFormModel.uploadFile({
  token: "share-token",
  fileName: "note.txt",
  contentType: "text/plain",
  contentBase64: "aGVsbG8=",
});

get, submit, opened, uploadFile, and getFile do not send a bearer token. get returns a node whose schema is the published SurveyJS document, or empty when the form is closed. The node also carries defaultLocale, locales, the locale to activate, and captcha (provider, siteKey) when the form requires it. submit sends answers verbatim. source is Link or Embed. Leave the honeypot blank; a filled website is rejected before the request is sent. opened records that a per-lead link was opened. uploadFile checks the extension and content type first and returns a file node. getFile(token, formFileId) returns the bytes in contentBase64.

Webhooks and files

import { FileModel, WebhookModel } from "@blazeo.com/form-builder-client";

const hook = await WebhookModel.save({
  accountId: "account-id",
  formId: "form-id",
  url: "https://example.com/hooks/forms",
  secret: "a-secret-value-16",
  enabled: true,
});
const bytes = await FileModel.get("account-id", "file-id");

WebhookModel.get, save, and delete return a webhook node. The same methods on that node return the envelope and write the row back onto it. url must be absolute https, or http on localhost. FileModel.get sends the bearer token and keeps contentBase64 as the service sent it. FileModel.getPublic is the same read without a token.

Field mapping

import { MappingModel } from "@blazeo.com/form-builder-client";

const catalogue = await MappingModel.getLeadFields("account-id");
const mapping = await MappingModel.get("account-id", "form-id");
await mapping.save([
  { questionName: "email", leadFieldKey: "email" },
  { questionName: "notes", leadFieldKey: null },
]);
await mapping.setEnabled(true);

getLeadFields returns { fields } with key, label, and dataType. get returns the draft mapping for one form. mappedCount of questionCount is how many questions are matched. save replaces those draft rows. A blank leadFieldKey leaves the question unmatched. The rows apply to responses after the next publish. setEnabled turns mapping on or off. Instance get, save, and setEnabled write the updated mapping onto the same node.

Enrichment

import { EnrichmentModel, SubmissionModel } from "@blazeo.com/form-builder-client";

const review = await EnrichmentModel.listPending("account-id", "submission-id");
await review.approve(review.items[0].leadEnrichmentId, "user-id");
await review.dismissAll("user-id");

const submission = await SubmissionModel.get("account-id", "submission-id");
await submission.listPending();
await submission.approveAll("user-id");

listPending returns the pending rows for one submission. approve and dismiss resolve one row. approveAll and dismissAll resolve every pending row. resolvedBy is the CRM user making the decision. A second approve-all or dismiss-all is an empty success. The same methods on a submission node update that submission's enrichmentStatus and pendingEnrichmentCount and leave the rest of the submission in place.

Analytics

import { AnalyticsModel, FormModel } from "@blazeo.com/form-builder-client";

const analytics = await AnalyticsModel.summary("account-id", "form-id");
await analytics.timeseries({ from: "2026-09-18", to: "2026-10-17" });

const form = await FormModel.get("account-id", "form-id");
const cards = await form.summary();

summary returns the three Responses-tab cards: totalSubmissions, thisWeek, and averagePerDay. Each card has value and deltaPercent. A null delta means the previous period was zero. timeseries returns one point per day, including days with a count of zero. Omit from and to and the service uses the last 30 days. A date-only to includes that whole UTC day, and the range is at most 366 days. Loading the chart onto an analytics node leaves the cards in place. Calling either method on a form returns an analytics node and leaves the form unchanged.

Ping and outbox

import { AuthModel, OutboxModel } from "@blazeo.com/form-builder-client";

const ping = await AuthModel.ping();
const batch = await OutboxModel.dispatch({ batchSize: 25 });

ping calls GET /ping and does not send a bearer token. The node keeps message and utc as the route writes them. dispatch posts one outbox batch. batchSize is optional, from 1 to 200. Omit it and the service uses its configured size. The node reports picked, delivered, failedThisRun, and the callback errors from that run.

Root store

import { configure, createRootStore, FormModel } from "@blazeo.com/form-builder-client";

configure({ baseUrl: "https://localhost:7125" });
const store = createRootStore();
const form = store.addForm({ formId: "form-id", accountId: "account-id", name: "Intake" });
await form.get();

const loaded = await FormModel.get("account-id", "form-id");

createRootStore() needs configure({ baseUrl }) first. It holds maps of forms, versions, sends, submissions, and enrichment, keyed by their ids. addForm, addVersion, addSend, addSubmission, and addEnrichment put a node in the matching map. Adding the same id again updates that node. FormModel.get and the other static methods keep working when you never create a store.

Sample

sample/ is a Vite app that depends on this package with file:... Build the client first so dist/ exists, then start the sample:

npm run build
cd sample
npm install
npm run dev

Leave the base URL blank and the dev server proxies /ping, /Api, and /Public to https://localhost:7125. Type another base URL when the service is already reachable from the browser. Ping needs no token. Create token, then list forms for an account. The list is kept on createRootStore().

Build

npm run build writes dist/index.js (ESM), dist/index.cjs (CommonJS), and the type declarations. prepublishOnly runs that build. publishConfig.access is public, so npm publish from this folder publishes @blazeo.com/form-builder-client.