# 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.

Status: Ready (Smootify 2.0)  
Plan: Server plan  
Add-on: Metaobject creator  
URL: https://www.smootify.io/docs/astro/features/metaobject-creator  
Updated: 2026-10-21

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](https://www.smootify.io/docs/astro/features/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-draft` and approve them before they show (step 4). Without it, entries are saved without a status, and `data-draft` has 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.

```html
<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:

1. **Settings > Custom data > Metaobjects**: the definition Warranty registration, type `warranty_registration`, with the fields Serial number (key `serial_number`, single line text), Accepts reminders (key `accepts_reminders`, true or false) and Receipt (key `receipt`, file). **Active-draft status** on, to save entries as drafts with `data-draft`. **Storefronts** access only to show the entries on the site.
2. In the form, inside `metaobject-creator`, each field’s `name` is a key: a text input named `serial_number` sends what the visitor types; a checkbox named `accepts_reminders` sends `true` when checked, and `false` when unchecked; a `file-input` with `name="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”.
3. **Content > Metaobjects > Warranty registration**: the new entry, with its values. With `data-draft="true"` on `metaobject-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](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](https://www.smootify.io/docs/astro/features/metaobjects#5-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`.

```html
<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-multiple` with the most files allowed, such as `3`. 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](https://www.smootify.io/docs/astro/features/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:

1. In the definition, add a field of type **Customer** (a customer reference), with a key such as `customer`.
2. On `metaobject-creator`, add the attribute `customer-field` with that key: `customer-field="customer"`.

```html
<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-creator` gets the class `is-customer` and the form shows. Put a login link next to it for the others: see [Customer accounts](https://www.smootify.io/docs/astro/build/account).
- **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](https://www.smootify.io/docs/astro/reference/conditions/customer).

**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](https://www.smootify.io/docs/astro/features/metaobjects#the-logged-in-customers-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-param` links to the entry. Point the link to the page that shows one entry, such as `/gift-list`, and give `data-handle-param` the name of that page’s parameter, such as `l`: Smootify writes `/gift-list?l=<handle>` into it. That page shows the entry with `smootify-metaobject` and the same `data-handle-param`: see [The entry named in the page link](https://www.smootify.io/docs/astro/features/metaobjects#3-one-entry);
- a `button` with `data-action="share"` shares the same link: the device’s share sheet on phones, otherwise the link is copied. Add `data-copy` to always copy it, and `data-title` to name the share. For two seconds after a click, the button has the class `is-shared` or `is-copied`: show a “Link copied” text with that class.

```html
<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`.

```html
<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-field` of the entry. For the owner the element gets the class `is-owner`; for anyone else it stays hidden.
- **Delete.** A `button` with `data-action="delete"` deletes the entry and takes it out of the customer’s entries. `data-confirm` asks first with that text, and `data-redirect` opens that page afterwards. Without a redirect, the element gets the class `is-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](https://www.smootify.io/flows), each with the definition to create and a file to import: [review moderation](https://www.smootify.io/flows/review-moderation) and [review requests](https://www.smootify.io/flows/review-request), and alerts for [back in stock](https://www.smootify.io/flows/back-in-stock), a [waitlist](https://www.smootify.io/flows/waitlist) and a [price drop](https://www.smootify.io/flows/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

> [!STUCK]
> - **Nothing is saved, and `[form-fail]` names a field.** A field's `name` is not the key in Shopify: fix each `name`.
> - **Shopify refuses a measure.** The number has no unit: add a `select` with `data-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 `name` of `file-input` is not the key of the file field.
> - **The share link goes to the page without the entry.** The link's `data-handle-param` is not the parameter that the entry page's `smootify-metaobject` reads, or the link is outside `[form-done]`.
> - **The form never shows.** It has `customer-field` and the visitor is not logged in, or Customer Accounts is not set up on the site: see [Customer accounts](https://www.smootify.io/docs/astro/build/account).
> - **"This form cannot create entries of this metaobject type".** `data-type` is 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.
