Skip to main content

Form

The form component wraps form controls and enables implicit submission, allowing users to submit from any input by pressing Enter. Use form to group related input fields and handle form submission through JavaScript event handlers. Unlike HTML forms, form doesn't automatically submit data using HTTP.

You must register a submit event to process form data programmatically. If your handler performs async work, return a promise from it so the extension runtime can wait for the save to settle before tearing down. See Handle async submission.

The form component provides event callbacks for handling user interactions. Learn more about handling events.

Anchor to reset
reset
<typeof tagName> | null
required

A callback that is run when the form is reset.

Anchor to submit
submit
<typeof tagName> | null
required

A callback that is run when the form is submitted.


Group input fields that submit together when a merchant presses Enter or clicks a submit button. This example shows a basic form with a text field and submit button.

Preview

html

<s-form>
<s-text-field label="Email address" />
<s-button variant="primary" type="submit">Submit</s-button>
</s-form>

Anchor to Build a campaign form with date and money fieldsBuild a campaign form with date and money fields

Use specialized field types like date field and money field to collect structured campaign data. This example includes a reset button alongside submit so merchants can clear and start over.

Preview

html

<s-form>
<s-stack direction="block" gap="base">
<s-text-field label="Campaign name" name="campaign" required></s-text-field>
<s-date-field label="Start date" name="startDate" required></s-date-field>
<s-date-field label="End date" name="endDate"></s-date-field>
<s-money-field label="Budget" name="budget"></s-money-field>
<s-stack direction="inline" gap="base">
<s-button variant="primary" type="submit">Create campaign</s-button>
<s-button type="reset">Reset</s-button>
</s-stack>
</s-stack>
</s-form>

Anchor to Show field validation errorsShow field validation errors

Display specific error messages on individual fields to guide merchants toward valid input. This example shows a required text field and number field with inline validation errors.

Preview

html

<s-form>
<s-stack direction="block" gap="base">
<s-text-field label="Product title" name="title" required error="Product title is required"></s-text-field>
<s-number-field label="Price" name="price" min="0" step="0.01" prefix="$" error="Price must be greater than 0"></s-number-field>
<s-button variant="primary" type="submit">Create product</s-button>
</s-stack>
</s-form>

Anchor to Use select and checkbox fieldsUse select and checkbox fields

Mix text inputs with selects and checkboxes to capture different kinds of merchant decisions. This example combines a text field, a select dropdown, and a checkbox for creating a discount.

Preview

html

<s-form>
<s-stack direction="block" gap="base">
<s-text-field label="Discount code" name="code" required></s-text-field>
<s-select label="Discount type" name="type">
<s-option value="percentage">Percentage</s-option>
<s-option value="fixed">Fixed amount</s-option>
<s-option value="shipping">Free shipping</s-option>
</s-select>
<s-checkbox label="Apply to all products" name="allProducts" checked></s-checkbox>
<s-button variant="primary" type="submit">Create discount</s-button>
</s-stack>
</s-form>

Anchor to Group fields into sectionsGroup fields into sections

Organize a longer form into labeled groups so merchants can scan and complete it more easily. This example uses sections to separate contact information from shipping preferences.

Preview

html

<s-form>
<s-stack direction="block" gap="large">
<s-section heading="Contact information">
<s-stack direction="block" gap="base">
<s-text-field label="Full name" name="name" required></s-text-field>
<s-email-field label="Email address" name="email" required></s-email-field>
</s-stack>
</s-section>
<s-section heading="Shipping preferences">
<s-stack direction="block" gap="base">
<s-select label="Shipping speed" name="speed">
<s-option value="standard">Standard (5–7 days)</s-option>
<s-option value="express">Express (2–3 days)</s-option>
<s-option value="overnight">Overnight</s-option>
</s-select>
<s-checkbox label="Require signature on delivery" name="signature"></s-checkbox>
</s-stack>
</s-section>
<s-button variant="primary" type="submit">Save preferences</s-button>
</s-stack>
</s-form>

Anchor to Handle async submissionHandle async submission

Handle async work in a submit handler so the host waits for the save to settle before tearing down the extension runtime. Call event.waitUntil(promise) to signal that the form is still saving, and return the promise so the form integration can track completion. The optional chaining on event?.waitUntil?. keeps the handler safe in environments where the event or the API isn't available, such as local test harnesses.

jsx

import {render} from 'preact';
import {useState} from 'preact/hooks';

export default async () => {
render(<Extension />, document.body);
};

function Extension() {
const [question, setQuestion] = useState('');
const [answer, setAnswer] = useState('');

const handleSubmit = (event) => {
const promise = saveEntry({question, answer});
event?.waitUntil?.(promise);
return promise;
};

return (
<s-form onSubmit={handleSubmit}>
<s-stack direction="block" gap="base">
<s-text-field
label="Question"
name="question"
value={question}
onChange={(event) => setQuestion(event.target.value)}
required
/>
<s-text-area
label="Answer"
name="answer"
value={answer}
onChange={(event) => setAnswer(event.target.value)}
required
/>
<s-button variant="primary" type="submit">Save</s-button>
</s-stack>
</s-form>
);
}

async function saveEntry(entry) {
// Replace with your own persistence (shopify.storage, your backend, and so on).
await shopify.storage.set('faq:latest', entry);
}

  • Group related fields logically: Organize fields by category or workflow step so merchants can complete forms efficiently.
  • Validate with specific error messages: Instead of Invalid input, provide actionable feedback like Email must include @ symbol or Password must be at least 8 characters.
  • Mark required fields clearly: Use the required property and show validation errors only after user interaction or submission attempt.
  • Choose field types that match data: Use email field for emails, number field for quantities, and date field for dates to provide appropriate keyboards and pickers.
  • Provide submission feedback: Show loading states during processing and clear success or error messages after completion. Prevent duplicate submissions while processing.
  • Handle unsaved changes: For long or complex forms, consider auto-saving drafts or prompting before navigation when changes exist.
  • Return a promise from async submit handlers: When your submit handler performs async work, call event.waitUntil(promise) and return the promise. The host might unmount the extension runtime when a screen closes (for example, after a page-stack intent completes), so handlers that don't signal completion might be cut off before the save settles. See the Handle async submission example.

  • Unlike native HTML forms, the component doesn't automatically submit data using HTTP. You must register a submit event handler to process form data programmatically.

Was this page helpful?