Skip to content

Configuration reference ​

Every field of helppuff.json. Generated from the schema helppuff validates against, so it is always exact; helppuff schema prints the same as JSON Schema. For what these files are and how they relate to the dashboard, see Configuration.

Secrets are never values here: a field marked { env } takes the name of an environment variable, e.g. { "env": "OPENAI_API_KEY" }, whose value lives in .env and on the Worker.

Top level ​

FieldTypeDefaultDescription
$schemastringThe JSON Schema for editors and agents. Written by helppuff.
formatintegerThe helppuff.json format version. Absent means 1; helppuff upgrade updates it when a release changes the format. · ≥ 1
site (required)stringSite id: lowercase letters, digits and dashes. Appears in the embed snippet.
name (required)stringThe business name, as visitors see it. · 1–60 chars
websitestringThe website the assistant is for, and learns from. · URL
origins (required)string[]Every origin the widget may be embedded on. The preview page is added automatically. · ≥ 1 items
modelobjectWho writes the answers. Leave it out for Workers AI. Changed only here (or with helppuff model set), then helppuff deploy.
model.provider (required)"workers-ai" | "openai-compatible" | "openai" | "gemini" | "anthropic" | "custom""workers-ai" Workers AI, on your Cloudflare account. The default: no key, on the Free plan. · "openai-compatible" Any OpenAI-compatible /chat/completions API with tools. · "openai" OpenAI. · "gemini" Google Gemini (its OpenAI-compatible API). · "anthropic" Anthropic Claude (the Messages API). · "custom" Your own model: a TypeScript file whose default export is defineModel({ id, chat }). See the wiki's Custom model page.
model.modelstringA Workers AI model id. Default: GLM-4.7 Flash. · 1–200 chars
model.budgetobjectSpend guards. Other providers bill you directly; cap them with security.limits.messagesPerSitePerDay.
model.budget.dailyNeuronsintegerWorkers AI only: neurons per UTC day before answers stop (default 9,000; the free allocation is 10,000). Raise it on Workers Paid. · 0–10000000
model.budget.maxInputTokensintegerThe most tokens sent to the model per answer: prompt, passages and history, trimmed to fit. · 1000–100000
model.reasoning"low" | "medium" | "high""medium"How long the model thinks before answering: low, medium or high. Deeper is better on multi-step questions, slower to start, and the thinking is billed. Models that only switch thinking on or off treat every level as on.
model.fallbackModelstringTried once when the main model fails for any reason other than the budget. · 1–200 chars
model.localestringBCP 47, e.g. en-AU: spelling and date style of answers. · ≤ 35 chars
model.timezonestringIANA, e.g. Australia/Melbourne: "are you open now?". · ≤ 64 chars
model.maxAnswerSentencesinteger4The longest answer, in sentences. Short answers cost less and read better in a chat. · 1–20
model.maxOutputTokensinteger600A hard cap on tokens per answer. · 64–4096
model.historyMessagesinteger6Earlier messages sent with each question. The main cost lever after retrieval. · 0–24
model.richMessagesbooleantrueLet the model offer next-step chips (option buttons) after an answer.
model.toolsobject{}What the assistant can do besides answering.
model.tools.callbackbooleantrueOffer to have the team call or email back — the default way to a person.
model.tools.businessHoursbooleantrueAnswer "are you open now?" from the business hours and time zone.
model.tools.captureLeadbooleanRetired. Accepted from older configs and ignored.
model.tools.handoffbooleanRetired. Accepted from older configs and ignored.
model.tools.bookingbooleanRetired. Accepted from older configs and ignored.
model.businessobject{}Details the owner confirmed. Usually left empty: the details learned from the site, and edited in the dashboard, are used.
model.business.namestringThe business name. · ≤ 200 chars
model.business.phonestringThe main phone number. · ≤ 100 chars
model.business.emailstringThe contact email. · ≤ 200 chars
model.business.addressstringThe street address. · ≤ 400 chars
model.business.hoursstring[][]Opening hours, one line per day or range, e.g. "Mon-Fri 7am-5pm". · ≤ 14 items
model.business.serviceAreasstring[][]Suburbs or regions served. · ≤ 100 items
model.gatewaystringWith provider: "workers-ai". An AI Gateway id: caching, logs and rate limits in front of every Workers AI call. · 1–64 chars
model.preset"deepinfra" | "openrouter" | "deepseek" | "groq" | "together" | "mistral" | "fireworks" | "vercel-ai-gateway" | "cloudflare-ai-gateway"With provider: "openai-compatible". Fills baseUrl and the key's variable name: deepinfra, openrouter, deepseek, groq, together, mistral, fireworks, vercel-ai-gateway, cloudflare-ai-gateway.
model.baseUrlstringWith provider: "openai-compatible". The API base, before /chat/completions. Needed without a preset. · URL
model.apiKey{ env }With provider: "openai-compatible". The key, by environment variable name. Default: the preset's (e.g. DEEPINFRA_API_KEY).
model.headersmap of string | { env }With provider: "openai-compatible". Extra headers; a value may be a secret ({ env }).
model.nativeToolsbooleanWith provider: "openai-compatible". The model calls tools natively (default true). Off: tool calls it writes in its text are still read.
model.accountIdstringWith provider: "openai-compatible". cloudflare-ai-gateway: the account (default: the one deployed to).
model.gatewayIdstringWith provider: "openai-compatible". cloudflare-ai-gateway: the gateway (default: default). · 1–64 chars
model.gatewayToken{ env }With provider: "openai-compatible". cloudflare-ai-gateway: an authenticated gateway's token, sent as cf-aig-authorization.
model.apiKey{ env }{"env":"OPENAI_API_KEY"}With provider: "openai". Your OpenAI API key, by environment variable name.
model.apiKey{ env }{"env":"GEMINI_API_KEY"}With provider: "gemini". Your Gemini API key, by environment variable name.
model.apiKey{ env }{"env":"ANTHROPIC_API_KEY"}With provider: "anthropic". Your Anthropic API key, by environment variable name.
model.module (required)stringWith provider: "custom". The file, relative to helppuff.json, e.g. ./llm.ts. Bundled into the Worker at deploy.
model.secretsstring[]With provider: "custom". The environment variables your file reads (env.NAME): uploaded as Worker secrets at deploy. · ≤ 20 items
promptstring"prompt.md"Path to the system prompt, relative to helppuff.json.
assistantobject{}How the assistant behaves: goal, tone, answer length. HelpPuff writes these around prompt.md on every answer, so prompt.md holds only what is specific to the business.
assistant.goal"callbacks" | "answers" | "bookings""callbacks"What the assistant is for: callbacks (help, then get the team in touch), answers, or bookings (help, then send them to bookingUrl: a booking, sign-up or quote page).
assistant.tone"friendly" | "professional" | "casual""friendly"How it sounds.
assistant.length"short" | "detailed""short"short: a few sentences; detailed: complete answers with short lists.
assistant.prices"share" | "quote""share"share: give prices exactly as the site and documents state them; quote: never give a price or estimate, offer a quote from the team instead.
assistant.bookingUrlstringThe page the bookings goal sends visitors to: booking, sign-up or a quote form. · ≤ 2000 chars, URL
autoSummarybooleantrueAutomatically summarise and label finished conversations. Off keeps transcripts, after-chat tools and webhooks running.
liveobject{}Live chat: visitors can talk to a person on your team, who answers from the dashboard or Telegram. Off by default. helppuff live on turns it on.
live.enabledbooleanfalseLet visitors talk to a person on the team. When nobody is available they get the callback form instead.
live.waitSecondsinteger120How long a visitor waits for someone to take the chat before they are offered the callback form (they can keep waiting). · 15–3600
live.closeAfterMinutesinteger60A conversation with no message for this long is closed. A visitor who writes again is answered by the assistant. · 5–1440
live.showAgentNamebooleantrueShow visitors the first name of the person answering ("Sam joined"); off: "Someone from the team".
live.aiWhileWaitingbooleanfalseLet the assistant keep answering until someone takes the chat.
bookingsobject{}Appointments: mode none, link (offer a booking page) or picker (a date picker in the chat; HelpPuff books, the assistant never does). Left out, a booking link found on the website is offered. With the picker, sync picks the calendar (keys in Settings → Integrations, helppuff integrations). helppuff bookings changes them.
filesobject{}Photos and files on R2: visitors sending them in the chat (chat: off, live while a person has the chat, or always) and the team attaching them to jobs (jobs). Both off by default; helppuff files turns them on. Needs R2 on for the Cloudflare account (deploy makes the bucket).

backend ​

Only for a backend that runs the whole conversation itself: retell, http (your own API), echo, or openai / gemini with their own file stores. Otherwise leave it out and use model and knowledge.retrieval.

"type": "assistant" ​

HelpPuff's own assistant, set up by model and knowledge.retrieval. Implied when backend is left out; never written by hand.

FieldTypeDefaultDescription

"type": "workers-ai" ​

Workers AI with HelpPuff's own knowledge base. The default; runs on the Workers Free plan.

FieldTypeDefaultDescription
backend.modelstring"@cf/zai-org/glm-4.7-flash"The Workers AI model that writes answers. GLM-4.7 Flash is the best all-round on the Free plan; the wiki's AI models page compares the others for speed, quality and answers a day. · 1–200 chars
backend.reasoning"low" | "medium" | "high""medium"How long the model thinks before answering: low, medium or high. Deeper is better on multi-step questions, slower to start, and the thinking is billed. Models that only switch thinking on or off treat every level as on.
backend.fallbackModelstringTried once when the main model fails for any reason other than the budget. · 1–200 chars
backend.gatewaystringAn AI Gateway id: caching, logs and rate limits in front of every model call. · 1–64 chars
backend.localestringBCP 47, e.g. en-AU: spelling and date style of answers. · ≤ 35 chars
backend.timezonestringIANA, e.g. Australia/Melbourne: "are you open now?". · ≤ 64 chars
backend.maxAnswerSentencesinteger4The longest answer, in sentences. Short answers cost less and read better in a chat. · 1–20
backend.maxOutputTokensinteger600A hard cap on tokens per answer. · 64–4096
backend.historyMessagesinteger6Earlier messages sent with each question. The main cost lever after retrieval. · 0–24
backend.richMessagesbooleantrueLet the model offer next-step chips (option buttons) after an answer.
backend.retrievalobject{}How answers find passages in the knowledge base.
backend.toolsobject{}What the assistant can do besides answering.
backend.tools.callbackbooleantrueOffer to have the team call or email back — the default way to a person.
backend.tools.businessHoursbooleantrueAnswer "are you open now?" from the business hours and time zone.
backend.tools.captureLeadbooleanRetired. Accepted from older configs and ignored.
backend.tools.handoffbooleanRetired. Accepted from older configs and ignored.
backend.tools.bookingbooleanRetired. Accepted from older configs and ignored.
backend.handoffmap of anyRetired (callbacks replaced handoff). Accepted from older configs and ignored.
backend.businessobject{}Details the owner confirmed. Usually left empty: the details learned from the site, and edited in the dashboard, are used.
backend.business.namestringThe business name. · ≤ 200 chars
backend.business.phonestringThe main phone number. · ≤ 100 chars
backend.business.emailstringThe contact email. · ≤ 200 chars
backend.business.addressstringThe street address. · ≤ 400 chars
backend.business.hoursstring[][]Opening hours, one line per day or range, e.g. "Mon-Fri 7am-5pm". · ≤ 14 items
backend.business.serviceAreasstring[][]Suburbs or regions served. · ≤ 100 items
backend.budgetobject{}The daily Workers AI spend guard. Past it, visitors get your contact details and a callback form instead of answers.
backend.budget.dailyNeuronsinteger9000Neurons per UTC day before answers stop; the free allocation is 10,000. · 0–10000000
backend.budget.maxInputTokensinteger6000The most tokens sent to the model per answer — prompt, passages and history — trimmed to fit. · 1000–100000

backend.retrieval ​

How answers find passages in the knowledge base.

FieldTypeDefaultDescription
backend.retrieval.embeddingModelstring"@cf/baai/bge-m3"Turns pages and questions into vectors. Changing it re-learns the site (and uploaded files) on the next deploy. · 1–200 chars
backend.retrieval.rerankerModelstring | null"@cf/baai/bge-reranker-base"Re-reads the passages search found and keeps only those that answer the question. null turns it off: about half a second faster, but more likely to answer from the wrong page.
backend.retrieval.topKVectorinteger20Passages taken from meaning (vector) search before re-scoring. · 1–50
backend.retrieval.topKKeywordinteger20Passages taken from keyword (full-text) search before re-scoring. · 1–50
backend.retrieval.finalKinteger4Passages the model reads for each answer. · 1–10
backend.retrieval.minScorenumber0.2Passages scored below this are dropped. With none left, the assistant says it is not sure and offers a callback instead of guessing. · 0–1
backend.retrieval.queryRewrite"heuristic" | "llm" | "off""heuristic"heuristic: follow-ups borrow the previous question. llm: one extra small call rewrites it.
backend.retrieval.intentModelstring | nullnulle.g. @cf/cloudflare/clef-flash: classify each question to steer retrieval. Off by default.

"type": "cloudflare" ​

Cloudflare AI Search: retrieval and generation managed by Cloudflare.

FieldTypeDefaultDescription
backend.instancestringInstance name on your account. Created by helppuff deploy if it does not exist.
backend.endpointstringUse an existing public endpoint instead of a binding, e.g. https://search.example.com · URL
backend.modelstringWorkers AI model id, or an AI Gateway alias. Empty uses the instance's own model.
backend.maxResultsintegerPassages AI Search retrieves per question. · 1–50

"type": "openai" ​

OpenAI (Responses API), or any compatible endpoint with baseUrl.

FieldTypeDefaultDescription
backend.modelstring"gpt-5-mini"The OpenAI model.
backend.apiKey{ env }{"env":"OPENAI_API_KEY"}Your OpenAI API key, by environment variable name.
backend.vectorStoreIdstringFilled in by helppuff knowledge sync.
backend.retrieval"helppuff"helppuff: answer from HelpPuff's own knowledge base (crawled by the Worker) instead of a vector store.
backend.promptIdstringA stored prompt in the OpenAI dashboard; overrides prompt.md.
backend.baseUrlstringAnother OpenAI-compatible Responses endpoint, e.g. Azure OpenAI. · URL

"type": "gemini" ​

Google Gemini, with File Search.

FieldTypeDefaultDescription
backend.modelstring"gemini-3-flash"The Gemini model.
backend.apiKey{ env }{"env":"GEMINI_API_KEY"}Your Gemini API key, by environment variable name.
backend.fileSearchStorestringFilled in by helppuff knowledge sync.
backend.retrieval"helppuff"helppuff: answer from HelpPuff's own knowledge base instead of File Search.

"type": "anthropic" ​

Anthropic Claude, grounded in AI Search or HelpPuff's own knowledge base.

FieldTypeDefaultDescription
backend.modelstring"claude-opus-5"The Claude model.
backend.apiKey{ env }{"env":"ANTHROPIC_API_KEY"}Your Anthropic API key, by environment variable name.
backend.effort"low" | "medium" | "high" | "xhigh" | "max"How hard Claude thinks before answering. Lower is faster and cheaper.
backend.knowledgebooleanGround answers in a Cloudflare AI Search instance. On by default when there is knowledge.
backend.retrieval"helppuff"helppuff: ground answers in HelpPuff's own knowledge base instead of AI Search.
backend.instancestringInstance name on your account. Created by helppuff deploy if it does not exist.
backend.endpointstringUse an existing public endpoint instead of a binding, e.g. https://search.example.com · URL

"type": "http" ​

Your own API.

FieldTypeDefaultDescription
backend.url (required)stringYour API's base URL. · URL
backend.mode"helppuff" | "openai""helppuff"helppuff: your API speaks the HelpPuff backend protocol. openai: any /chat/completions endpoint.
backend.modelstringopenai mode: the model name your endpoint expects.
backend.token{ env }Sent as Authorization: Bearer ….
backend.signingSecret{ env }Signs every request with HMAC-SHA256 so your API can verify it.
backend.streambooleantrueStream replies as they are written.

"type": "retell" ​

A Retell chat agent.

FieldTypeDefaultDescription
backend.agentId (required)stringThe Retell chat agent id.
backend.apiKey{ env }{"env":"RETELL_API_KEY"}Your Retell API key, by environment variable name.
backend.retrieval"helppuff"helppuff: crawl the site into HelpPuff's knowledge base; the agent searches it via a custom function (see helppuff status).

"type": "echo" ​

Echoes what it is sent, with a demo of every widget feature. For development; needs no key.

FieldTypeDefaultDescription

knowledge ​

What the assistant learns from: the website, and your own files.

FieldTypeDefaultDescription
knowledge.websiteboolean | objecttrueLearn from the website: true (the defaults), false, or which pages and how often.
knowledge.website.maxPagesintegerDefault: 300 for workers-ai (crawled by the Worker), 50 for the others (crawled here). · 1–1000
knowledge.website.includestring[]Only crawl URLs matching one of these: a substring, or a glob like **/services/**.
knowledge.website.excludestring[]Never crawl URLs matching one of these. Defaults leave out privacy, terms, tags, carts and accounts.
knowledge.website.renderJs"auto" | "always" | "never"workers-ai: render pages drawn by JavaScript with Browser Rendering (auto = only when needed).
knowledge.website.schedule"off" | "daily" | "weekly" | "monthly"workers-ai: re-crawl the selected pages on this schedule.
knowledge.filesstring[][]Files and folders to index, relative to helppuff.json. PDF, Markdown, text, HTML, DOCX.
knowledge.retrievalobjectWhat answers come from. Left out: HelpPuff's own knowledge base. none for none. Changed only here (or with helppuff rag set), then helppuff deploy.
knowledge.retrieval.type (required)"helppuff" | "none" | "ai-search" | "openai-vector-store" | "http" | "custom""helppuff" HelpPuff's own knowledge base: your site and files, learned by the Worker (Vectorize + D1). · "none" No knowledge base: the prompt and the business details only. · "ai-search" Cloudflare AI Search, searched for passages; your model writes the answer. · "openai-vector-store" An OpenAI vector store you fill in OpenAI, searched for passages. · "http" Your own search over HTTP: POST url { query, question, siteId, limit } → { passages: [{ title, content, url? }] }. · "custom" Your own knowledge base: a TypeScript file whose default export is defineRetriever({ id, search }). See the wiki's Custom knowledge base page.
knowledge.retrieval.embeddingModelstring"@cf/baai/bge-m3"With type: "helppuff". Turns pages and questions into vectors. Changing it re-learns the site (and uploaded files) on the next deploy. · 1–200 chars
knowledge.retrieval.rerankerModelstring | null"@cf/baai/bge-reranker-base"With type: "helppuff". Re-reads the passages search found and keeps only those that answer the question. null turns it off: about half a second faster, but more likely to answer from the wrong page.
knowledge.retrieval.topKVectorinteger20With type: "helppuff". Passages taken from meaning (vector) search before re-scoring. · 1–50
knowledge.retrieval.topKKeywordinteger20With type: "helppuff". Passages taken from keyword (full-text) search before re-scoring. · 1–50
knowledge.retrieval.finalKinteger4With type: "helppuff". Passages the model reads for each answer. · 1–10
knowledge.retrieval.minScorenumber0.2With type: "helppuff". Passages scored below this are dropped. With none left, the assistant says it is not sure and offers a callback instead of guessing. · 0–1
knowledge.retrieval.queryRewrite"heuristic" | "llm" | "off""heuristic"With type: "helppuff". heuristic: follow-ups borrow the previous question. llm: one extra small call rewrites it.
knowledge.retrieval.intentModelstring | nullnullWith type: "helppuff". e.g. @cf/cloudflare/clef-flash: classify each question to steer retrieval. Off by default.
knowledge.retrieval.instancestringWith type: "ai-search". Instance name on your account. Created by helppuff deploy if it does not exist.
knowledge.retrieval.endpointstringWith type: "ai-search". Use an existing public endpoint instead of a binding, e.g. https://search.example.com · URL
knowledge.retrieval.maxResultsintegerWith type: "ai-search". Passages per question. · 1–50
knowledge.retrieval.vectorStoreId (required)stringWith type: "openai-vector-store". The vector store id, e.g. vs_….
knowledge.retrieval.apiKey{ env }{"env":"OPENAI_API_KEY"}With type: "openai-vector-store". An OpenAI API key that can read it.
knowledge.retrieval.url (required)stringWith type: "http". Your endpoint. · URL
knowledge.retrieval.token{ env }With type: "http". Sent as Authorization: Bearer ….
knowledge.retrieval.module (required)stringWith type: "custom". The file, relative to helppuff.json, e.g. ./rag.ts.
knowledge.retrieval.secretsstring[]With type: "custom". The environment variables your file reads: uploaded as Worker secrets at deploy. · ≤ 20 items

security ​

Rate limits, daily cap, Turnstile. The defaults are safe for a public site.

FieldTypeDefaultDescription
security.captchaobjectCheck new chats (and dashboard sign-ins) with Cloudflare Turnstile (invisible for most visitors). Strongly recommended: without it, a script can start chats.
security.captcha.provider (required)"turnstile"Cloudflare Turnstile.
security.captcha.siteKey (required)stringThe Turnstile site key (public). · 1–200 chars
security.captcha.secret (required){ env }The Turnstile secret key, by environment variable name.
security.limitsobject{}Per-visitor and per-site limits. The daily cap is the cost backstop.
security.signInobject{}Dashboard sign-in limits.
security.signIn.attemptsPerIpinteger10Sign-in attempts (and one-time link checks) one IP may make per window. · 1–1000
security.signIn.attemptsPerAccountinteger5Failed sign-ins one email may have per window, from anywhere. Counts failures only. · 1–1000
security.signIn.windowMinutesinteger15The window both sign-in limits count over, in minutes. · 1–1440
security.signIn.captchabooleantrueAsk for Turnstile on the sign-in form when security.captcha is set. Add the dashboard's hostname to the Turnstile widget.
security.allowIpsstring[][]IP addresses or CIDR ranges exempt from the per-visitor limits (your office, a monitor). The per-chat and daily caps still apply. · ≤ 500 items
security.blockIpsstring[][]IP addresses or CIDR ranges refused by the chat. The dashboard is not affected. · ≤ 500 items
security.sessionTtlHoursnumber24How long a chat can be continued (the widget keeps it across pages and reloads). · 0.25–720

security.limits ​

Per-visitor and per-site limits. The daily cap is the cost backstop.

FieldTypeDefaultDescription
security.limits.messagesPerIpPerMinuteinteger10Messages one visitor (IP) may send a minute. · 1–600
security.limits.messagesPerIpPerDayinteger100Messages one visitor (IP) may send a day (UTC), so one visitor cannot use up the daily cap. · 1–100000
security.limits.sessionsPerIpPerHourinteger5New chats one visitor (IP) may start an hour. · 1–1000
security.limits.sessionsPerIpPerDayinteger20New chats one visitor (IP) may start a day (UTC). · 1–10000
security.limits.messagesPerSessioninteger60Messages in one chat before the visitor must start another. · 1–1000
security.limits.messagesPerSitePerDayinteger500The cost backstop. Always set this. · 1–1000000
security.limits.maxMessageLengthinteger1000The longest message a visitor may send, in characters. · 1–4000
security.limits.maxLeadFieldLengthinteger200The longest answer to one form field, in characters (a message box gets maxLeadMessageLength). · 20–2000
security.limits.maxLeadMessageLengthinteger2000The longest answer to a message box in a form, in characters. · 20–4000
security.limits.feedbackPerIpPerMinuteinteger30Thumbs up or down one visitor (IP) may give a minute. · 1–600
security.limits.pollsPerIpPerMinuteinteger120Checks for new messages one visitor (IP) may make a minute (backends that reply later, like Retell). · 1–600
security.limits.endsPerIpPerMinuteinteger10Chats one visitor (IP) may close a minute. · 1–600
security.limits.retellLookupsPerMinuteinteger120Knowledge-base lookups a Retell agent may make a minute, for the whole site. · 1–6000
security.limits.apiRequestsPerKeyPerMinuteinteger120Requests a new API key may make a minute (each key can have its own). Chat requests also count against the daily cap. · 1–6000
security.limits.apiKeysPerSiteinteger50Active API keys a site may have. · 1–500
security.limits.handoversPerIpPerDayinteger3Times one visitor (IP) may ask for a person a day (live chat). · 1–1000
security.limits.waitingPerSiteinteger20Live chats that may wait for a person at once; past it, visitors get the callback form. · 1–1000
security.limits.liveSocketsPerIpinteger3Live chat connections one visitor (IP) may hold open at once. · 1–100
security.limits.slotLookupsPerIpPerMinuteinteger30Times one visitor (IP) may load free times in the booking calendar a minute. · 1–600
security.limits.bookingsPerIpPerHourinteger5Appointments one visitor (IP) may book, move or cancel an hour, from the chat. · 1–100
security.limits.uploadsPerIpPerHourinteger20Photos and files one visitor (IP) may send an hour, when visitors can send files (files.chat). · 1–1000

leads ​

Where leads go besides the dashboard.

FieldTypeDefaultDescription
leads.webhookstring | { env }POST each lead to this URL (or to the URL in this environment variable). For every event, add webhooks in the dashboard instead.

dashboard ​

The dashboard at <worker>/admin: conversations, leads, knowledge, analytics and settings, stored in D1 on your account.

FieldTypeDefaultDescription
dashboard.enabledbooleantrueServe the dashboard at <worker>/admin.
dashboard.adminEmailstringThe owner's email, for backends without a setup link. workers-ai: set on the setup page instead. · email
dashboard.summaryModelstringWorkers AI model used for conversation summaries.

cloudflare ​

Written by helppuff deploy. Safe to commit; holds no secrets.

FieldTypeDefaultDescription
cloudflare.accountIdstringThe Cloudflare account deployed to.
cloudflare.workerNamestringThe Worker's name. Default: knowtific-helppuff-<site>.
cloudflare.urlstringWhere the Worker answers. · URL
cloudflare.kvNamespaceIdstringThe KV namespace (live config, new-conversation counters).
cloudflare.d1DatabaseIdstringThe D1 database (conversations, leads, knowledge).
cloudflare.vectorizeIndexstringThe Vectorize index (knowledge vectors).
cloudflare.filesBucketstringThe R2 bucket (photos and files), when the account has R2.

widget ​

Brand, launcher, home screen, lead form, flows — see helppuff schema.

widget.brand ​

Names, colour and theme.

FieldTypeDefaultDescription
widget.brand.namestring"Chat"The business name in the widget. · 1–60 chars
widget.brand.agentNamestring"Assistant"The assistant's name, in its header and messages. · 1–60 chars
widget.brand.avatarstringThe assistant's picture: a square image URL. · 1–2048 chars
widget.brand.accentstring"#5B5BF7"The main colour (hex). Text on it is made readable automatically.
widget.brand.theme"light" | "dark" | "auto""auto"auto follows the visitor's system setting.
widget.brand.tokensmap of stringFine-grained styling: CSS custom properties without the --hp- prefix, e.g. { "radius-panel": "12px", "font": "Inter, sans-serif" }.

widget.launcher ​

The button that opens the chat.

FieldTypeDefaultDescription
widget.launcher.position"bottom-right" | "bottom-left""bottom-right"Which corner the button sits in.
widget.launcher.offsetobjectMove the button away from the corner, e.g. above a cookie banner.
widget.launcher.offset.x (required)numberPixels from the side. · 0–200
widget.launcher.offset.y (required)numberPixels from the bottom. · 0–200
widget.launcher.labelstringText beside the orb, or inside it when shape is pill. For example "Chat with us". · ≤ 40 chars
widget.launcher.icon"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person""chat"Which of the built-in icons the launcher shows.
widget.launcher.shape"orb" | "pill""orb"orb is the signature circle. pill widens it to sit the label inside the button, which reads as a clearer invitation on a busy page.
widget.launcher.hideOnPathsstring[]Pages where the widget does not appear, e.g. /checkout/**. · ≤ 50 items

widget.home ​

The first screen visitors see.

FieldTypeDefaultDescription
widget.home.titlestring"Hi there"The heading of the first screen. · ≤ 120 chars
widget.home.subtitlestring"Ask anything, or pick a shortcut."The line under it. · ≤ 240 chars
widget.home.shortcutsobject[]Buttons on the first screen: suggested questions, a call button, a form… · ≤ 8 items
widget.home.shortcuts[].id (required)stringUnique among the shortcuts. · 1–64 chars
widget.home.shortcuts[].label (required)stringWhat the shortcut says. · 1–80 chars
widget.home.shortcuts[].descriptionstringA line under the label. · ≤ 160 chars
widget.home.shortcuts[].icon"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person"A built-in icon.
widget.home.shortcuts[].action (required)objectWhat happens when the button is pressed. kind picks it.
widget.home.shortcuts[].action.kind (required)"reply" | "url" | "tel" | "email" | "flow" | "form" | "booking""reply" Send a message as the visitor. · "url" Open a page. · "tel" Call a number. · "email" Write an email. · "flow" Start one of widget.flows. · "form" Open one of widget.forms. · "booking" Open the booking calendar in the chat (Settings → Bookings: date picker in the chat).
widget.home.shortcuts[].action.id (required)stringAn id, unique within its list. · 1–64 chars
widget.home.shortcuts[].action.label (required)stringWhat the button says. · 1–120 chars
widget.home.shortcuts[].action.value (required)stringWith kind: "reply". The message sent. · 1–500 chars
widget.home.shortcuts[].action.url (required)stringWith kind: "url". The page to open. · 1–2048 chars
widget.home.shortcuts[].action.newTabbooleanWith kind: "url". Open in a new tab.
widget.home.shortcuts[].action.phone (required)stringWith kind: "tel". The number to call. · 1–40 chars
widget.home.shortcuts[].action.email (required)stringWith kind: "email". The address to write to. · 3–200 chars
widget.home.shortcuts[].action.flowId (required)stringWith kind: "flow". The flow's id. · 1–64 chars
widget.home.shortcuts[].action.formId (required)stringWith kind: "form". The form's id. · 1–64 chars
widget.home.shortcuts[].pathsstring[]Show it only on these pages. · ≤ 20 items
widget.home.linksobjectA list of useful pages on the first screen.
widget.home.links.title (required)stringThe list's heading. · ≤ 120 chars
widget.home.links.items (required)object[]The links. · 1–10 items
widget.home.links.items[].label (required)stringThe link text. · 1–160 chars
widget.home.links.items[].url (required)stringWhere it goes (https). · 1–2048 chars
widget.home.links.items[].descriptionstringA line under the link. · ≤ 300 chars

widget.leadForm ​

The short form before the chat.

FieldTypeDefaultDescription
widget.leadForm.enabledbooleantrueAsk for details before the chat starts.
widget.leadForm.titlestringThe form's heading. · ≤ 120 chars
widget.leadForm.fieldsobject[][{"name":"name","label":"Name","type":"text","required":true,"autocomplete":"name"},{"name":"phone","label":"Phone","type":"tel","required":true,"autocomplete":"tel"}]The questions, in order. Add your own; each answer is kept on the lead and shown to the assistant. · 1–12 items
widget.leadForm.fields[].name (required)stringThe key the answer is stored under. name, email, phone and message mean something to the assistant (a message opens the chat). · 1–64 chars
widget.leadForm.fields[].label (required)stringWhat the visitor sees. · 1–160 chars
widget.leadForm.fields[].type (required)"text" | "email" | "tel" | "textarea" | "select"The kind of input. select needs options.
widget.leadForm.fields[].requiredbooleanMust be filled in.
widget.leadForm.fields[].placeholderstringHint text inside the empty field. · ≤ 160 chars
widget.leadForm.fields[].optionsstring[]The choices of a select. · ≤ 50 items
widget.leadForm.fields[].patternstringA regular expression the answer must match. · ≤ 200 chars
widget.leadForm.fields[].autocompletestringThe HTML autocomplete hint, e.g. email, tel. · ≤ 64 chars
widget.leadForm.submitLabelstringThe button's text. · ≤ 60 chars
widget.leadForm.privacyobjectA privacy note under the form.
widget.leadForm.privacy.text (required)stringThe notice, e.g. "We only use this to reply to you." · 1–300 chars
widget.leadForm.privacy.url (required)stringYour privacy policy. · 1–2048 chars
widget.leadForm.askFirstMessagebooleanAdd a box for the visitor's first message to the form. A field named message does the same and can be labelled.

widget.chat ​

The conversation screen.

FieldTypeDefaultDescription
widget.chat.placeholderstringHint text in the message box. · ≤ 120 chars
widget.chat.initialMessagesstring[]The assistant's greeting, before the visitor writes. · ≤ 3 items
widget.chat.shortcutsobject[]Buttons above the message box during the chat. · ≤ 8 items
widget.chat.shortcuts[].id (required)stringUnique among the shortcuts. · 1–64 chars
widget.chat.shortcuts[].label (required)stringWhat the shortcut says. · 1–80 chars
widget.chat.shortcuts[].descriptionstringA line under the label. · ≤ 160 chars
widget.chat.shortcuts[].icon"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person"A built-in icon.
widget.chat.shortcuts[].action (required)objectWhat happens when the button is pressed. kind picks it.
widget.chat.shortcuts[].action.kind (required)"reply" | "url" | "tel" | "email" | "flow" | "form" | "booking""reply" Send a message as the visitor. · "url" Open a page. · "tel" Call a number. · "email" Write an email. · "flow" Start one of widget.flows. · "form" Open one of widget.forms. · "booking" Open the booking calendar in the chat (Settings → Bookings: date picker in the chat).
widget.chat.shortcuts[].action.id (required)stringAn id, unique within its list. · 1–64 chars
widget.chat.shortcuts[].action.label (required)stringWhat the button says. · 1–120 chars
widget.chat.shortcuts[].action.value (required)stringWith kind: "reply". The message sent. · 1–500 chars
widget.chat.shortcuts[].action.url (required)stringWith kind: "url". The page to open. · 1–2048 chars
widget.chat.shortcuts[].action.newTabbooleanWith kind: "url". Open in a new tab.
widget.chat.shortcuts[].action.phone (required)stringWith kind: "tel". The number to call. · 1–40 chars
widget.chat.shortcuts[].action.email (required)stringWith kind: "email". The address to write to. · 3–200 chars
widget.chat.shortcuts[].action.flowId (required)stringWith kind: "flow". The flow's id. · 1–64 chars
widget.chat.shortcuts[].action.formId (required)stringWith kind: "form". The form's id. · 1–64 chars
widget.chat.shortcuts[].pathsstring[]Show it only on these pages. · ≤ 20 items
widget.chat.fallbackContactobjectHow to reach a person when the assistant is unavailable (an outage, or the daily budget is spent).
widget.chat.fallbackContact.phonestringShown when the assistant cannot answer. · ≤ 40 chars
widget.chat.fallbackContact.emailstringShown when the assistant cannot answer. · ≤ 200 chars

widget.teaser ​

A message that pops up beside the button to invite a chat.

FieldTypeDefaultDescription
widget.teaser.text (required)stringThe message, e.g. "Need a quote? Ask me." · 1–200 chars
widget.teaser.delayMsintegerTime on the page. Omit to rely on afterScroll alone. · 2000–120000
widget.teaser.afterScrollintegerPercentage of the page scrolled, 1–100. Whichever trigger fires first shows the teaser; a visitor who reads rather than waits still sees it. · 1–100
widget.teaser.pathsstring[]Show it only on these pages. · ≤ 50 items
widget.teaser.oncePerSessionbooleantrueShow it once per visit, not on every page.

widget.flows ​

Guided questions asked in the widget (no AI), sent as one message at the end. Type: object[].

FieldTypeDefaultDescription
widget.flows[].id (required)stringWhat a flow action or shortcut starts it by. · 1–64 chars
widget.flows[].steps (required)object[]Questions asked one at a time, in the widget, before anything is sent. · 1–10 items
widget.flows[].steps[].field (required)stringThe name the answer is kept under, for the template. · 1–64 chars
widget.flows[].steps[].ask (required)stringThe question, shown as the assistant's message. · 1–400 chars
widget.flows[].steps[].input (required)"text" | "choice" | "phone" | "email"How the visitor answers.
widget.flows[].steps[].choicesstring[]The buttons of a choice step. · ≤ 12 items
widget.flows[].steps[].requiredbooleanThe step cannot be skipped.
widget.flows[].submit (required)objectWhat happens with the answers: message sends them as one message, job saves them as a job.
widget.flows[].submit.as (required)"message" | "job""message" Sent as one visitor message. · "job" Saved as a job (the site's Jobs pipeline), with the answers as its fields; the visitor gets its number.
widget.flows[].submit.template (required)stringThe message, with {{field}} for each answer, e.g. "Quote for {{service}} in {{suburb}}". · 1–1000 chars

widget.forms ​

Inline forms by id, opened by a form action or shortcut. Submitting one sends its answers to the assistant. Type: map of object.

FieldTypeDefaultDescription
widget.forms.<id>.titlestringThe form's heading. · ≤ 120 chars
widget.forms.<id>.fields (required)object[]The questions. · 1–12 items
widget.forms.<id>.fields[].name (required)stringThe key the answer is stored under. name, email, phone and message mean something to the assistant (a message opens the chat). · 1–64 chars
widget.forms.<id>.fields[].label (required)stringWhat the visitor sees. · 1–160 chars
widget.forms.<id>.fields[].type (required)"text" | "email" | "tel" | "textarea" | "select"The kind of input. select needs options.
widget.forms.<id>.fields[].requiredbooleanMust be filled in.
widget.forms.<id>.fields[].placeholderstringHint text inside the empty field. · ≤ 160 chars
widget.forms.<id>.fields[].optionsstring[]The choices of a select. · ≤ 50 items
widget.forms.<id>.fields[].patternstringA regular expression the answer must match. · ≤ 200 chars
widget.forms.<id>.fields[].autocompletestringThe HTML autocomplete hint, e.g. email, tel. · ≤ 64 chars
widget.forms.<id>.submitLabelstringThe button's text. · ≤ 60 chars

widget.sound ​

A soft sound when a reply arrives.

FieldTypeDefaultDescription
widget.sound.enabled (required)booleanPlay it.

widget.captcha ​

Set by security.captcha; you do not need to set it here.

FieldTypeDefaultDescription
widget.captcha.provider (required)"turnstile"Cloudflare Turnstile.
widget.captcha.siteKey (required)stringThe Turnstile site key. · 1–200 chars

widget.poweredBy ​

Ignored: the widget always shows "Powered by HelpPuff". Accepted so older configs still load. Type: boolean | object.

widget.strings ​

UI string overrides. Keys are validated by the widget, not here. Type: map of string.

MIT licensed. Runs in your own Cloudflare account.