Qaid
ARTICLE

Getting Started with Quests

Build a quest in the dashboard, paste its snippet onto your page, and start collecting step-by-step answers.

Qaid Team
TL;DR

Build and publish a quest in the dashboard, then paste the QUEST CODE snippet from Customize into your page. The embed loads the live version from /api/quests/{id}/definition and saves every answer to /api/quests/responses. It shows inline in a container you name, or as a modal when you name none.

A quest is a short form that asks one question per step. @qaiddev/quests-embed draws it on your page and saves each answer as the visitor gives it. You write the questions in the dashboard, and the embed fetches whatever version you last put live.

Make the quest in the dashboard

  1. Open Quests

    Open your project and click Quests in its header.

  2. Pick a template

    Under Start a new quest, click a template. Untitled quest starts with one empty question. The editor opens on your new draft.

  3. Write and publish it

    Edit the questions, then click Save as new version. That makes it live. A strip then reads “Embed loads from” and shows the quest’s definition URL.

Loading the film…

Every other editor control, from the JSON view to rollback, is in Build a quest in the dashboard.

Put it on your page

Open Customize in the project header. Under QUEST PREVIEW, pick your quest in Preview quest, then copy the QUEST CODE block. It carries your key, the endpoint and the quest’s URL:

<div id="quest-mount"></div>
<script
  src="https://unpkg.com/@qaiddev/quests-embed"
  defer
  data-api-key="YOUR_PROJECT_KEY"
  data-endpoint="https://qaid.dev/api/quests/responses"
  data-config-url="https://qaid.dev/api/quests/YOUR_QUEST_ID/definition"
  data-container="#quest-mount"
></script>

The script starts on its own once it loads, with or without defer. It needs data-endpoint and data-config-url, or it does nothing.

The quest ID is the last part of the editor’s URL, and it is also inside the “Embed loads from” link. Use the ID, not the slug shown in the quest list: a slug gets a 404.

Install with npm

In an app that renders in the browser, build the embed yourself:

npm install @qaiddev/quests-embed
import { QaidQuests } from "@qaiddev/quests-embed";

const quest = new QaidQuests({
  endpoint: "https://qaid.dev/api/quests/responses",
  configUrl: "https://qaid.dev/api/quests/YOUR_QUEST_ID/definition",
  apiKey: "YOUR_PROJECT_KEY",
  container: "#quest-mount",
});

// On a route change:
quest.destroy();

The ESM button on the QUEST CODE block prints this form for you.

Show it inline or as a modal

With container, the quest draws inside that element and joins the page’s normal tab order. Give the element a height and the card fills it, scrolling inside with a “more below” cue.

Leave container out and the quest opens as a centred modal over a backdrop. It traps focus, and Escape or a click on the backdrop closes it. Three options only matter in this mode: modalWidth (480), backdropOpacity (0.4) and zIndex (50).

To open a modal from a button, build the embed in the click handler:

document.querySelector("#feedback-button").addEventListener("click", () => {
  new QaidQuests({
    endpoint: "https://qaid.dev/api/quests/responses",
    configUrl: "https://qaid.dev/api/quests/YOUR_QUEST_ID/definition",
    apiKey: "YOUR_PROJECT_KEY",
  });
});

Each new instance starts a new response, so do not build one on every page load unless you want it open.

Know when someone finished

Option or method What it does
onComplete(answers) Runs once, after the server takes the submit and the thank-you screen shows. Gets a copy of the answers to the questions that showed, keyed by question ID
onClose() Runs once when the embed goes away: the visitor closed the modal, or you called destroy()
metadata An object sent with the new response. qaid.dev keeps feedbackId and ignores other keys
getAnswers() The answers so far
getCurrentQuestionId() The ID of the step on screen
destroy() Removes the embed and its listeners
new QaidQuests({
  endpoint: "https://qaid.dev/api/quests/responses",
  configUrl: "https://qaid.dev/api/quests/YOUR_QUEST_ID/definition",
  apiKey: "YOUR_PROJECT_KEY",
  container: "#quest-mount",
  onComplete: (answers) => {
    console.log("Finished", answers.satisfaction);
  },
});

Question types

A quest has five question types. The editor’s Add question row names them Text, Currency, Range, Date and Choice.

Type Settings Answer saved as Checked before Next
text placeholder, multiline, inputType (text, email, tel, url), minLength, maxLength string Not empty when required; minLength only when required
currency currency (ISO code, default USD), locale, min, max, placeholder number Not empty when required; within min and max
range min, max (both needed), step, defaultValue, unit number Nothing: a slider always holds a value
date min, max (YYYY-MM-DD) string, YYYY-MM-DD Not empty when required. min and max only limit the picker
multiple-choice options (value, label, description, image), multiple, imageAlignment string, or an array when multiple Something picked when required

Every type also takes id, label, description, required and visibleIf. A currency field is a plain number box; only its symbol follows the locale.

Visitors can use the keyboard: Enter moves on, Cmd or Ctrl+Enter moves on from a multi-line box, and the number keys 1 to 9 pick choices. autoAdvance: true moves on as soon as someone picks a single-choice option.

What gets saved, and when

  1. When the quest loads, the embed creates a response. The dashboard lists it as In progress.
  2. Each answer is saved as it changes. Typing is batched: saveDebounceMs, 500 by default.
  3. The last step’s button submits, and the response becomes Submitted.

So a visitor who leaves halfway still leaves their answers. A new visit starts a new response; nothing is restored on reload.

The server only takes the response if the key is live, the page is on the project’s domain (localhost always passes), and the quest has a live version. See the answers on the quest’s Responses page.

If the server refuses the response or the submit, or the network drops, the last step shows “Couldn’t send your answers” with a Try again button, and Close in a modal. The answers stay, and onComplete does not run. A wrong key or a blocked domain ends here. This needs quests-embed 1.5.3 or later.

A definition is cached for up to 60 seconds, so a new version can take a minute to reach your page.

Options

Option Data attribute Default
endpoint (required) data-endpoint
configUrl data-config-url
questionnaire none; JSON config only
apiKey data-api-key
container data-container none, so a modal
autoAdvance data-auto-advance false
saveDebounceMs data-save-debounce-ms 500
autoFocus none true
progressPosition none the quest’s setting, then "top"
modalWidth, backdropOpacity, zIndex data-modal-width, data-backdrop-opacity, data-zindex 480, 0.4, 50

Colours, fonts, presets and themes have their own page: Style the quests embed.

questionnaire takes a quest’s JSON inline and wins over configUrl. qaid.dev still stores answers only for a published dashboard quest, so inline JSON suits previews and your own backend.

Where to go next

  1. Branching with visibleIf: ask a question only after a given answer.
  2. Ask follow-up questions: open a quest after a thumbs click.
  3. Read responses and analytics: what came back.
Back to all articles