Getting Started with Quests
Build a quest in the dashboard, paste its snippet onto your page, and start collecting step-by-step answers.
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
Open Quests
Open your project and click Quests in its header.
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.
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.
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
- When the quest loads, the embed creates a response. The dashboard lists it as In progress.
- Each answer is saved as it changes. Typing is batched:
saveDebounceMs, 500 by default. - 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
- Branching with
visibleIf: ask a question only after a given answer. - Ask follow-up questions: open a quest after a thumbs click.
- Read responses and analytics: what came back.