Collect data with forms
A form that saves each submission as a metaobject entry in Shopify, with file uploads, drafts to moderate, and automations in Shopify Flow.
With metaobject-creator, a <form> saves each submission as a metaobject entry in Shopify, instead of sending it to an endpoint of your own. The data sits in the same place as your products, orders and customers. You can show it again on the site, and Shopify Flow can react to it. Use it for warranty registrations, reseller applications, event sign-ups or reviews.
Note
The metaobject creator needs the Server plan and Metaobject creator turned on in the Smootify dashboard. It runs on the site’s licensed domain.
A plain form or the metaobject creator
A plain <form> sends its data to the endpoint or the script you write. Inside metaobject-creator, the same form saves each entry in Shopify, next to your products and customers. You can show the entries again on the site with Show metaobjects, and Shopify Flow can start an automation for each one.
1. Create the definition in Shopify
Under Settings > Custom data > Metaobjects, add a definition with the fields you want to collect. Turn on:
- Active-draft status, to save entries as drafts with
data-draftand approve them before they show (step 4). Without it, entries are saved without a status, anddata-drafthas no effect; - Storefronts access, if you want to show the entries on the site. Saving works without it.
Write down the definition’s type, such as warranty_registration, and the key of each field, such as serial_number for “Serial number”.
2. Build the form
Write the element metaobject-creator with the attribute data-type set to the type, and put a <form> inside it. Without a form, the element is removed.
<metaobject-creator data-type="warranty_registration" data-draft="true">
<form>
<input name="serial_number">
<input name="purchase_date">
<label><input type="checkbox" name="accepts_reminders"> Remind me before the warranty ends</label>
<file-input name="receipt"></file-input>
<button type="submit" data-wait="Sending…">Register</button>
</form>
<div form-done hidden>Thank you. Your product is registered.</div>
<div form-fail hidden>Something went wrong</div>
</metaobject-creator>Field names are the keys. The name of each field is the key of a field of the definition, such as serial_number. From the definition to the entry, for three of the fields above:
- Settings > Custom data > Metaobjects: the definition Warranty registration, type
warranty_registration, with the fields Serial number (keyserial_number, single line text), Accepts reminders (keyaccepts_reminders, true or false) and Receipt (keyreceipt, file). Active-draft status on, to save entries as drafts withdata-draft. Storefronts access only to show the entries on the site. - In the form, inside
metaobject-creator, each field’snameis a key: a text input namedserial_numbersends what the visitor types; a checkbox namedaccepts_reminderssendstruewhen checked, andfalsewhen unchecked; afile-inputwithname="receipt"sends the file, which goes to the store’s Shopify Files. Empty fields are not sent, and neither are fields whose name starts with_: use those for fields that only help the visitor, such as “repeat your email”. - Content > Metaobjects > Warranty registration: the new entry, with its values. With
data-draft="true"onmetaobject-creator, it is a draft until you make it active.
What each field type takes. Smootify’s server turns what the form sends into the format of each field of the definition, so most fields need only the plain HTML control. What the visitor writes, and what Shopify saves:
| Field type in Shopify | In the form | The visitor writes → Shopify saves |
|---|---|---|
| Single line text, Multi-line text | an input or a textarea | Linen → Linen |
| Rich text | a textarea: each line is a paragraph | two lines → two paragraphs |
| Integer, Decimal | an input | 4,5 → 4.5 |
| Rating | an input, a select or radio buttons, with a number on the field’s scale | 4 → 4 out of 5 |
| Money | an input, in your store’s currency | 9,90 → 9.90 |
| Dimension, volume, weight | an input for the number, and a select with data-unit-for set to that field’s name, whose options are the units; or one input with the number and its unit | 2,5 and cm → 2.5 cm |
| True or false | one checkbox with that name | checked → true, not checked → false |
| A list of any type | several checkboxes with the same name, each with a value; a select with multiple; or an input with the values separated by commas | Red and Green ticked → Red, Green |
| Date | an input with type="date" | 2026-10-02 → 2 October 2026 |
| Date and time | an input with type="datetime-local" | a date and a time → that date and time |
| URL | an input | smootify.io → https://smootify.io |
| Product, variant, collection, page or metaobject reference | an input, or an input with type="hidden", holding the id | 9304502009994 → that product |
| Customer reference | customer-field on metaobject-creator | the logged-in customer |
| File, list of files | file-input, with data-multiple for a list | the uploaded files, in Shopify Files |
A value already in Shopify’s own format passes as it is. For a measure, the select with data-unit-for is not sent as a field of its own, so its name does not matter. A number without a unit is refused by Shopify, with its reason in the form’s [form-fail].
How many of each product. Next to each product checkbox, in the same card, an input type="number" with data-quantity-for set to the checkboxes’ name (data-quantity-for="products") says how many of that product the entry asks for, such as 6 glasses on a gift list. Give the number inputs the name of a JSON field of the definition, such as quantities: Smootify sends, for each checked product, its quantity, at least 1. The products of a gift list page then show how many are gifted: see Product cards from a field.
The product and the variant on the page. A form inside a smootify-product, such as a back-in-stock alert, can save which variant the visitor was looking at: a hidden input with the attribute variant="id" takes the current variant’s ID and follows every change of option. Give it the name of a variant reference field of the definition, such as variant.
<input type="hidden" name="variant" variant="id">Required fields. Add the native required attribute to the field. A field hidden by a condition does not count.
The form’s messages. [form-done] shows once the entry is saved, and the form hides. When Shopify refuses the entry, [form-fail] shows Shopify’s reason in its text, such as a field that does not exist or a required field left empty. Both start with hidden. The submit button’s data-wait text shows while it saves.
Fields that are not saved. Every filled field is sent, and a field whose key is not in the definition makes Shopify refuse the whole entry. For a field the entry does not need, such as a checkbox to accept your terms, start its name with _, such as _accepted: Smootify does not send it. Add required to stop the form until it is checked.
3. Upload files
For a file field, add the element file-input inside the form, with name set to the field’s key. The files go to your store’s Shopify Files, and the entry gets them.
- For a field that holds a list of files, add
data-multiplewith the most files allowed, such as3. Without a number, the limit is 5. - You design the upload area with the same parts and limits as the file uploader, such as a drop zone, a list of files and
max-file-size: see File uploads. - While a file is still uploading, the form cannot be sent.
Shopify Files has its own limits on size and file types. For larger files, upload them with another service and give the definition a URL field instead of a file field: the form sends the file’s address in a field with that field’s name. A file field does not take a file stored outside Shopify.
4. Moderate with drafts
With data-draft="true" on metaobject-creator, each entry is saved as a draft. It stays off the site until you make it active in Shopify. Use it for anything visitors write that others will read, such as reviews. Without it, entries are active as soon as they are sent.
5. Link the entry to the customer
To know which customer sent each entry, such as a warranty registration or a reseller application:
- In the definition, add a field of type Customer (a customer reference), with a key such as
customer. - On
metaobject-creator, add the attributecustomer-fieldwith that key:customer-field="customer".
<metaobject-creator data-type="warranty_registration" customer-field="customer">
<form>
<input name="serial_number">
<button type="submit" data-wait="Please wait…">Register</button>
</form>
<div form-done hidden>Thank you. Your product is registered.</div>
<div form-fail hidden>Something went wrong.</div>
</metaobject-creator>- No field to add. You do not add the customer to the form. Smootify’s server finds out who is logged in and writes the customer into that field. A value sent with the form under that key is ignored, so nobody can send an entry in another customer’s name.
- Only for logged-in customers. Until the customer logs in, the form is hidden and cannot be filled. Once they are logged in,
metaobject-creatorgets the classis-customerand the form shows. Put a login link next to it for the others: see Customer accounts. - The session must be valid. When the login has expired, the form shows “Log in to send this form” in its
[form-fail].
Only for some customers. To offer the form only to some customers, such as resellers you approved, put it inside an element with customer-tagged="reseller", or with if-customer-metafield on a metafield of the customer. A Shopify Flow workflow can then add or remove the tag. These conditions only show or hide the form on the page: see customer conditions.
The customer’s entries in the account. With customer-field, Smootify’s server also keeps a list of the customer’s entries: each new entry goes first into the customer’s metafield smootify.<type>, such as smootify.gift_list. The metafield is created the first time, and keeps the 128 newest entries. An account page lists them with smootify-metaobjects: see The logged-in customer’s entries.
6. Share the new entry
Once the entry is saved, metaobject-creator gets the class is-created and the attribute data-handle with the entry’s handle. Inside [form-done]:
- a link with
data-handle-paramlinks to the entry. Point the link to the page that shows one entry, such as/gift-list, and givedata-handle-paramthe name of that page’s parameter, such asl: Smootify writes/gift-list?l=<handle>into it. That page shows the entry withsmootify-metaobjectand the samedata-handle-param: see The entry named in the page link; - a
buttonwithdata-action="share"shares the same link: the device’s share sheet on phones, otherwise the link is copied. Adddata-copyto always copy it, anddata-titleto name the share. For two seconds after a click, the button has the classis-sharedoris-copied: show a “Link copied” text with that class.
<metaobject-creator data-type="gift_list" customer-field="customer">
<form>…</form>
<div form-done hidden>
<p>Your list is ready</p>
<a href="/gift-list" data-handle-param="l">Open your list</a>
<button type="button" data-action="share" data-title="My gift list">Copy the link</button>
</div>
</metaobject-creator>7. Let the customer edit or delete the entry
metaobject-editor opens an entry the logged-in customer created and lets them change it or delete it, such as their gift list or a warranty registration. It takes the same attributes as the form that created the entry: data-type, customer-field and, for the entry named in the page link, data-handle-param.
<metaobject-editor data-type="gift_list" customer-field="customer" data-handle-param="l">
<form>
<input name="title">
<textarea name="message"></textarea>
<button type="submit" data-wait="Saving…">Save</button>
</form>
<div form-done hidden>Saved.</div>
<div form-fail hidden>Something went wrong.</div>
<button type="button" data-action="delete" data-confirm="Delete this list?" data-redirect="/account/gift-lists">Delete the list</button>
</metaobject-editor>- The form opens filled in with the entry: texts, checkboxes, radios, selects, quantities and
file-input. Saving changes the entry. - Only its owner sees it. Smootify’s server checks that the logged-in customer is the one in the
customer-fieldof the entry. For the owner the element gets the classis-owner; for anyone else it stays hidden. - Delete. A
buttonwithdata-action="delete"deletes the entry and takes it out of the customer’s entries.data-confirmasks first with that text, anddata-redirectopens that page afterwards. Without a redirect, the element gets the classis-deleted. - It needs the Server plan and Metaobject creator, like the form that creates the entries.
8. React with Shopify Flow
The free Shopify Flow app can start an automation each time an entry is created: send an email, tag a customer, update a metafield, call another service. Install it in Shopify, then create a workflow that starts when a metaobject entry is created. With customer-field, the entry already holds the customer, so the workflow can tag them or update their metafields.
Ready-made workflows are in the Flow library, each with the definition to create and a file to import: review moderation and review requests, and alerts for back in stock, a waitlist and a price drop.
Tip
These forms often collect personal data. Tell visitors what you collect and why, and decide which of your Shopify staff can see the entries.
Before you publish
Check these first
- Nothing is saved, and
[form-fail]names a field. A field’snameis not the key in Shopify: fix eachname. - Shopify refuses a measure. The number has no unit: add a
selectwithdata-unit-for, or let the visitor write the unit. - The form disappears on the live site. The Server plan or Metaobject creator is missing, or the page is not on the licensed domain: Smootify then removes the element.
- Entries are saved but do not show on the site. The definition has no Storefronts access, or the entries are drafts.
- Files are missing from the entry. The
nameoffile-inputis not the key of the file field. - The share link goes to the page without the entry. The link’s
data-handle-paramis not the parameter that the entry page’ssmootify-metaobjectreads, or the link is outside[form-done]. - The form never shows. It has
customer-fieldand the visitor is not logged in, or Customer Accounts is not set up on the site: see Customer accounts. - “This form cannot create entries of this metaobject type”.
data-typeis a definition the form cannot write: Shopify’s own (shopify--…), an app’s ($app:…), or one of Smootify’s, such as the swatch options or the Magic Box. Create a definition of your own.