Skip to content

Add forms that continue in AI chat

Capture contact and inquiry forms in your chat inbox, notify your team, and let visitors continue the conversation with AI.

Published

Add data-aeo-form to a normal HTML form to send its fields to Answer Cloud.

The submission creates a new conversation in the existing chat inbox, saves the

submitted fields, and queues a notification for the organization's team. The AI responds using the same site content and chat

experience as the chat widget. Visitors can continue the conversation there.

No API key or separate form service is required. The public organization and

site IDs identify the destination; they are not secrets.

Add a form to a hosted site

Answer Cloud injects embed.js and the site's settings when publishing a hosted

site. Add the following markup to an Astro page or component. Do not add another

embed script, AI SDK, or local form endpoint.

<form data-aeo-form="Contact" method="post">
  <div>
    <label for="contact-name">Name</label>
    <input id="contact-name" name="name" autocomplete="name" required maxlength="100">
  </div>
  <div>
    <label for="contact-email">Email</label>
    <input id="contact-email" name="email" type="email" autocomplete="email"
           required maxlength="254">
  </div>
  <div>
    <label for="contact-phone">Phone (optional)</label>
    <input id="contact-phone" name="phone" type="tel" autocomplete="tel" maxlength="100">
  </div>
  <fieldset>
    <legend>I'm interested in (optional)</legend>
    <label><input type="checkbox" name="interests" value="Consulting"> Consulting</label>
    <label><input type="checkbox" name="interests" value="Support"> Support</label>
  </fieldset>
  <div>
    <label for="contact-message">How can we help?</label>
    <textarea id="contact-message" name="message" rows="5" required
              maxlength="5000"></textarea>
  </div>
  <button type="submit">Send message</button>
  <p data-aeo-form-status role="status" aria-live="polite"></p>
</form>
<noscript>Please enable JavaScript to send this form.</noscript>

Apply the site's existing form styles. Keep visible labels, a visible keyboard

focus indicator, and readable pending, success, and error text. Use unique IDs

if the page contains more than one form.

data-aeo-form="Contact" names the submission. Use a descriptive name such as

Request a quote or Book a demo. Every field needs a name; use conventional

name, first_name, last_name, email, phone, and company names for contact

details. Other named fields are saved in the conversation too.

Add a form to an external website

Set window.aeoSettings before loading the embed script. Replace 123 and 456

with the organization and site IDs for the intended site. Keep an existing

embed installation and add only the form when the script is already present.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Contact us</title>
  </head>
  <body>
    <main>
      <h1>Contact us</h1>
      <form data-aeo-form="Contact" method="post">
        <div>
          <label for="name">Name</label>
          <input id="name" name="name" autocomplete="name" required maxlength="100">
        </div>
        <div>
          <label for="email">Email</label>
          <input id="email" name="email" type="email" autocomplete="email"
                 required maxlength="254">
        </div>
        <div>
          <label for="message">Message</label>
          <textarea id="message" name="message" rows="5" required maxlength="5000"></textarea>
        </div>
        <button type="submit">Send message</button>
        <p data-aeo-form-status role="status" aria-live="polite"></p>
      </form>
      <noscript>Please enable JavaScript to send this form.</noscript>
    </main>
    <script>
      window.aeoSettings = {
        organization_id: 123,
        site_id: 456,
        chat: { enabled: true }
      };
    </script>
    <script src="https://app.answer.cloud/embed.js" defer></script>
  </body>
</html>

Forms also work when chat.enabled is false: the visitor receives inline

status and the team still receives the submission. On a hosted site, use the

platform's generated settings rather than replacing window.aeoSettings.

Submission behavior

  • The browser applies native constraints such as required, type="email",

and maxlength before a normal submission. These do not replace server-side

business validation in a custom integration.

  • The handler sends named text inputs, textareas, selects, and checked choices.

Repeated names and multiple selected values become arrays. Disabled,

password, file, hidden, unchecked, unnamed, and button controls are excluded.

  • Submit controls are disabled while the request is pending. A second submit

during that request does not create another request. Controls are restored

afterward.

  • The status element shows progress, success, or an error. If no descendant

with data-aeo-form-status exists, the handler creates an accessible status

element.

  • Field values stay in the form after both success and failure. A later

deliberate submission creates another conversation.

  • Each accepted form starts a new chat conversation. With chat enabled, the

widget opens that conversation and subsequent messages use the normal chat

endpoint. This replaces the widget's current conversation history for the

site.

  • Add data-aeo-open-chat="false" to the form to keep the visitor on the page

with inline status instead of opening the widget automatically.

The server saves contact fields directly; notification delivery does not depend

on the AI recognizing an email address. If AI generation fails, the saved

submission receives a fallback acknowledgment and the team notification is

still queued. A successful response confirms capture, not email delivery or a

promised response time from the team.

This integration requires JavaScript and a network connection. File uploads and

offline or native HTML POST fallbacks are not supported. Use it for contact and

inquiry forms; do not collect passwords, payment details, or other secrets.

The handler does not retry automatically. A network interruption or server

error can occur after the server has saved a submission, so check the chat

inbox before resubmitting when avoiding a duplicate matters.

Customize the page after submission

Listen on the form for aeo:form-success or aeo:form-error. Success event

detail contains the parsed API response; error event detail.error contains

the error object. These events let a site update its own UI alongside the

provided status and chat behavior.

<script>
  const form = document.querySelector('[data-aeo-form="Contact"]');
  form.addEventListener("aeo:form-success", (event) => {
    console.log("Saved conversation:", event.detail.conversation.id);
  });
  form.addEventListener("aeo:form-error", (event) => {
    console.warn(event.detail.error.message);
  });
</script>

Call the API from a custom form

POST https://app.answer.cloud/api/form_submissions accepts JSON without an

authorization header. Always send the intended organization_id; include

site_id and the current site_origin for a hosted site so the conversation

and AI retrieval stay scoped to it.

const response = await fetch("https://app.answer.cloud/api/form_submissions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    organization_id: 123,
    site_id: 456,
    site_origin: window.location.origin,
    form: {
      name: "Contact",
      fields: {
        name: "Jane Example",
        email: "jane@example.com",
        message: "Can you help with our project?",
        interests: ["Consulting", "Support"]
      }
    }
  })
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || "Unable to send the form. Please try again.");
// data.conversation, data.user_message, data.ai_message, data.references

Optional session_id and tracking use the same payload as /api/messages.

When the embed is available, obtain them with

window.AEOTracking.buildPayload(): send its session_id and use its

custom_data as tracking. The embed-managed form handler already does this.

The successful response is 201 Created and has the same shape as a chat

message response:

{
  "conversation": { "id": 789, "organization_id": 123, "site_id": 456 },
  "user_message": { "id": 1001, "role": "user", "content": "Form submission: Contact..." },
  "ai_message": { "id": 1002, "role": "assistant", "content": "Thanks for reaching out..." },
  "references": []
}

The objects include additional chat attributes. Save conversation.id and pass

it as conversation_id to /api/messages to continue that conversation. The

custom API call does not itself open or update the widget; the embed's form

handler provides that integration.

The API accepts at most 50 fields. Form and field names must be single-line text

without null characters and are limited to 100 characters, each value to 5,000 characters, and the combined form name, field

names, and values to 20,000 characters. Each field can contain text, a number,

a boolean, null, or an array of at most 50 text values. Scalars are converted

to trimmed text; null becomes empty text. Nested objects and submissions with

no nonempty value are rejected. The form name defaults to Contact form when

omitted or blank.

Handle non-success responses as errors and preserve the visitor's input. An

unknown organization or an out-of-scope conversation returns 404; managed

hosted sites also reject unknown site IDs or mismatched origins with 404.

Invalid submissions return 422 Unprocessable Entity with JSON such as

{"error":"form must contain a nonempty value"}.

Accepted forms record a server-side form_submit conversion with the

conversation and form identifiers. Field values are not included in the

analytics event. The embed's generic form tracking skips these forms to avoid

counting the same submission twice.

Verify the implementation

Use a test organization or coordinate any real submission with the site owner:

successful tests queue team notifications.

  1. Check labels, keyboard navigation, narrow screens, and native required/email

validation.
2. Submit a short test inquiry. Confirm the inline success state, retained

values, and the intended chat opening behavior.
3. Confirm the new conversation appears in the correct site's chat inbox with

all submitted fields and contact details. Send a chat follow-up and confirm

it stays in that conversation.
4. Confirm a single conversion and, separately, notification delivery.
5. Simulate a failed request locally; confirm the error is announced, controls

recover, and field values remain available for retry.

For hosted sites, verify the deployed page: a local Astro preview does not

include the generated embed integration.

Related documentation

Built for the answer era

Build a website people can use & AI can understand.

Build and deploy with your coding agent, or add an AI companion to your existing website.