@wix/agent-skills 1.18.0 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,129 @@
1
1
  ---
2
2
  name: "Create Form"
3
- description: Creates a form with fields (name, email, etc.) using the Form Schemas API. Covers field configuration, layout, and post-submission triggers.
3
+ description: "Creates a visitor-fillable Wix form with Form Schemas v4 — a contact or enquiry form, a signup or waitlist, an application, a survey, a quote request, and forms whose submissions create a contact. Ships a complete create request, plus the field table for every kind Wix supports — dropdown, choice, file upload, rating, address, payment, and the silent breakers that produce an empty or invisible form. Changing a form that already exists is Update Form."
4
4
  ---
5
5
  # RECIPE: Create a Wix Form
6
6
 
7
- > **Standard call shape (every curl below).** The `<AUTH>` placeholder is shorthand for `Authorization: Bearer <TOKEN>` only. Body-bearing requests also need `Content-Type: application/json`.
7
+ > **Standard call shape (every curl below).** The `<AUTH>` placeholder is shorthand for `Authorization: Bearer <TOKEN>` only. Body-bearing requests also need `Content-Type: application/json`. Send `wix-site-id: <SITE_ID>` when the token is account-scoped.
8
8
 
9
- Create a form on a Wix site that appears in the Forms & Submissions dashboard. The form collects visitor information (e.g., name, email) and can automatically upsert contacts on submission.
9
+ Create a form on a Wix site that appears in the **Forms & Submissions** dashboard and can be placed in the Editor. Wix Forms backs **any form a visitor fills in** — a contact or enquiry form, a signup or waitlist, an application, a survey, a quote request, a registration questionnaire, etc. Whether a submission also becomes a CRM contact is the optional per-field `contactMapping` (§ Contact fields).
10
+
11
+ **Two exceptions — route there instead:**
12
+
13
+ | The ask | Owner |
14
+ |---------------------------|-------|
15
+ | RSVP to an event | **Wix Events**, which ships its own registration form — [Create Event](../events/create-wix-event.md) |
16
+ | A bookable service's form | **Wix Bookings** |
17
+
18
+ **Flow:** STEP 0 confirm the app, read the caps → STEP 1 compose the fields → STEP 2 one POST, all fields → STEP 3 verify the read-back (**mandatory** — the `200` proves nothing).
10
19
 
11
20
  ---
12
21
 
13
- ## Create the form
22
+ ## Silent breakers
23
+
24
+ Four things are accepted with a **`200`** and produce a form that is empty, wrong, or invisible. There is no error to react to, so get them right on the first call — each is settled in the step named after it.
25
+
26
+ 1. **App — Wix Forms (New) `225dd912-7dea-4738-8688-4b8c6955ffc2` (STEP 0 · 1).** `14ce1214-b278-a7e4-1373-00cebd1bef7c` is the **Old** app: never install it, and never treat its presence as satisfying this API. *Get it wrong:* `UNSUPPORTED_FORM_NAMESPACE`; automations whose trigger belongs to the new app report "Forms app is not installed" even though *an* app named Wix Forms is installed.
27
+
28
+ 2. **Namespace — `wix.form_app.form` (STEP 2).** *Get it wrong:* a form under any other namespace (notably the **non-existent `wix.form_platform.form`**) reads back fine over the API and is **completely invisible** in the Forms dashboard and the Editor.
29
+
30
+ 3. **`identifier` — one of the predefined values in [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Field types.** *Get it wrong:* the field works over the API but the **Wix Forms editor cannot render it**, so the owner can't see or edit it. A form whose fields all carry invented identifiers opens **empty** in the editor.
31
+
32
+ 4. **Layout — every field, `SUBMIT_BUTTON` included, placed in `steps[].layout`, with lowercase GUIDs on both sides.** *Get it wrong:* the form appears in the Editor's form picker but renders **empty** — fields still store values, but nothing shows.
33
+
34
+ **Never create throwaway "test" forms to probe the shape.** The site's form allowance is finite (§ Plan caps), and probing burns it. Assemble the whole form and POST once, then verify (STEP 3).
35
+
36
+ ---
37
+
38
+ ## STEP 0: Preflight — confirm the app, then read the caps
39
+
40
+ **1 · Confirm Wix Forms (New) is installed.**
41
+
42
+ ```bash
43
+ curl -X GET \
44
+ 'https://www.wixapis.com/apps-installer-service/v1/app-instances' \
45
+ -H 'Authorization: <AUTH>'
46
+ ```
47
+
48
+ Look for `225dd912-7dea-4738-8688-4b8c6955ffc2` in the response — see [List Installed Apps](../app-installation/list-installed-apps.md). If it isn't installed, install **`225dd912-7dea-4738-8688-4b8c6955ffc2`** via the [Install Wix Apps](../app-installation/install-wix-apps.md) recipe. A fresh install returns `appInstance.status: "UNKNOWN"` until it propagates; if the first create fails with an identity/propagation error, retry **once** — do not loop.
14
49
 
15
- Call the Create Form endpoint with the `wix.form_app.form` namespace. The Wix Forms app (appDefId: `14ce1214-b278-a7e4-1373-00cebd1bef7c`) is usually already installed on sites.
50
+ **2 · Confirm the namespace is available** — [List Forms Providers Configs](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/list-forms-providers-configs):
51
+
52
+ ```bash
53
+ curl -X GET \
54
+ 'https://www.wixapis.com/form-schema-service/v4/forms/providers-config' \
55
+ -H 'Authorization: <AUTH>'
56
+ ```
57
+
58
+ Use this to confirm `wix.form_app.form` is among the namespaces the site can create form schemas in. **Do not use it to decide how many fields you can build.**
59
+
60
+ > **⚠️ `restrictions` here are NOT the site's limits.** A provider app declares them **once, for all sites**, in its app dashboard — so `maxForms` / `maxFields` / `maxDeletedForms` describe the app, not this site. The Wix Forms app separately derives the site's **real** form, field, step, condition and email-recipient limits (and whether premium-only field types are allowed) from its **premium plan**, and enforces them itself on every create and update. A missing `restrictions` object means default platform limits apply — not "unlimited".
61
+ >
62
+ > This is why free and unpublished sites have been seen reporting `maxFields: 150` / `maxForms: 150` here while the create rejects with **`Field count reached its limit of 10`** and **`Steps count reached its limit of 3`**. The config is not wrong; it answers a different question. **The create call is the only authority on what a site allows** — build the form you were asked for, and if it returns a count error, go to § Plan caps and put the choice to the user. A rejected create costs nothing: it consumes no form slot, so reading its errors is not "probing" (that rule is about leaving throwaway forms behind).
63
+
64
+ **3 · Only if you need a free slot:** list what exists — `GET https://www.wixapis.com/form-schema-service/v4/forms?namespace=wix.form_app.form&fieldsets=METADATA` (repeat with `&enabled=false`) — and `DELETE https://www.wixapis.com/form-schema-service/v4/forms/{formId}` **only** for forms that are obviously the install's own default sample (a "Get in touch" form with `first_name` / `email` / `message`). The site may hold the owner's **real** forms. If it isn't obviously sample data, **ask the user first** — never delete real content unprompted.
65
+
66
+ ---
67
+
68
+ ## STEP 1: Compose the fields
69
+
70
+ **Read [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) before composing anything.** It is the authority on every field-level rule and is not repeated here: the `identifier` / `inputType` / `componentType` table for every field kind, how the two nested block names are derived, choice fields' twin declarations, Ricos checkbox labels, display fields, the `contactField` values and their extra-detail keys, the layout that orders fields (including matching a lowercase `id` with a lowercase `fieldId`), and which fields need a premium plan or another Wix app. Value shapes on the way back out are [About Submission Values](https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/about-submission-values).
71
+
72
+ The rules below are the ones those articles don't state.
73
+
74
+ > **⚠️ "Must agree" is literal, and this is the one thing that really does make a field vanish.** The server builds a field's renderable view by dispatching on its `componentType` *within* its `inputType`. A pair that isn't valid together — `inputType: STRING` with `componentType: CHECKBOX_GROUP`, say — produces no view, and the field disappears without an error. Take both values from the same row of § Field types; never mix rows.
75
+
76
+ - **`required` goes at `inputOptions.required`** — beside `target` and `inputType` — **NOT** inside `validation`. A `required` key inside `stringOptions.validation` (or any `<inputType>Options.validation`) is accepted at create and **silently discarded**: the form ships with nothing mandatory and no error anywhere. `validation` carries value constraints only (`format`, `enum`, `minimum`, `minLength`, `items`). The one exception is the multi-line address field, whose per-subfield flags genuinely live at `addressOptions.validation.fields.<sub>.required` (subfield **visibility** is separate, at `multilineAddressOptions.fieldSettings.addressLine2.show`).
77
+ - **`id` must be a fresh lowercase GUID.** Generate them in the shell (`uuidgen | tr 'A-Z' 'a-z'`) — never type one from memory, never reuse the examples here. The server stores `id` lowercase: an **uppercase** `id` still saves, but the layout's `fieldId` no longer matches it, so the field is **silently unplaced** and the form renders empty.
78
+ - **`target` is the immutable submission key** — a unique lowercase `snake_case` string per field (e.g. `first_name_f409`). It is the contract every submission and every frontend binding uses.
79
+ - **Every example value you author is visitor-visible.** A `placeholder`, label or hint carrying a phone number, postcode, currency or date must follow the **site's** country, not a US/UK default.
80
+
81
+ ### Choice fields — the identifier is what routes the renderer
82
+
83
+ **A choice field declares its choices twice, and both declarations are required.** Every option carries its own lowercase GUID `id`, a `value` and a `label`; `validation.enum` (for `STRING`) or `validation.items.stringOptions.enum` + `itemType` (for `ARRAY`) lists every one of those `value`s. **An empty `validation` is a free-text field, not a dropdown** — the general "always include `validation`, even as `{}`" rule does not apply to a choice field, and getting this wrong is accepted with a `200`.
84
+
85
+ > **⚠️ A choice field whose `identifier` isn't the choice kind is stored as a plain text input.** `identifier: "TEXT_INPUT"` with `componentType: "DROPDOWN"` returns `200` and comes back as a `TEXT_INPUT` carrying `textInputOptions`: the identifier, not the `componentType`, routes the field to its renderer. **Diagnose it by which options block came home** — `textInputOptions` where you sent `dropdownOptions`, with a well-formed block and `enum`, means the `identifier`. Re-sending it, or delete-and-re-add with the same identifier, reproduces it.
86
+
87
+ > **⚠️ ARRAY fields fail *after* creation.** A malformed `arrayOptions.validation.items` (missing `itemType`, empty or omitted `items`) lists fine and counts in the summary, so STEP 3's checks 1–2 both pass — but **every** submission to the form then `400`s. STEP 3 check 3 (a live test submission) is the only proof.
88
+
89
+ A dropdown, in full — copy this and swap the label, target, options and every GUID:
90
+
91
+ ```json
92
+ {
93
+ "id": "a4f0c9d2-1e77-4b3e-9a52-7c1d8f6b4e30",
94
+ "identifier": "DROPDOWN",
95
+ "fieldType": "INPUT",
96
+ "inputOptions": {
97
+ "target": "job_industry_7f2b",
98
+ "required": false,
99
+ "inputType": "STRING",
100
+ "stringOptions": {
101
+ "validation": { "enum": ["Technology", "Healthcare", "Finance", "Other"] },
102
+ "componentType": "DROPDOWN",
103
+ "dropdownOptions": {
104
+ "label": "Job industry",
105
+ "showLabel": true,
106
+ "options": [
107
+ { "id": "1b8e5a71-3c04-4f9a-9d62-08b7a5cf3e41", "label": "Technology", "value": "Technology" },
108
+ { "id": "6d29c4b8-7f15-4a20-8e93-b04d7e2a9c58", "label": "Healthcare", "value": "Healthcare" },
109
+ { "id": "c37fa910-52d6-4e8b-b1a7-9f4c60d38e2b", "label": "Finance", "value": "Finance" },
110
+ { "id": "e8b1d6c4-90a3-42f7-8c05-3d7e15b9a642", "label": "Other", "value": "Other" }
111
+ ]
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Contact fields
119
+
120
+ > **⚠️ Never use `postSubmissionTriggers.upsertContact` to map contacts** Older recipes and samples used it, but it is a noop / response-only. Use per-field `contactMapping` instead.
121
+
122
+ ---
123
+
124
+ ## STEP 2: Create the form — one POST, all fields
125
+
126
+ **[Create Form](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/create-form) ships complete request examples** for a dozen form types (contact, survey, order, job application, booking, donation, waiver, billing …) — start from the closest one rather than assembling from scratch.
16
127
 
17
128
  ```bash
18
129
  curl -X POST \
@@ -21,151 +132,116 @@ curl -X POST \
21
132
  -H 'Authorization: <AUTH>' \
22
133
  -d '{
23
134
  "form": {
24
- "name": "Contact Form",
135
+ "name": "Contact form",
25
136
  "namespace": "wix.form_app.form",
26
- "enabled": true,
27
- "spamFilterProtectionLevel": "ADVANCED",
28
137
  "formFields": [
29
138
  {
30
- "id": "c1a2b3d4-0001-4a00-b000-000000000001",
31
- "hidden": false,
139
+ "id": "d7665e98-a7c4-4829-c104-fb856883e043",
32
140
  "identifier": "CONTACTS_FIRST_NAME",
33
141
  "fieldType": "INPUT",
34
142
  "inputOptions": {
35
- "target": "first_name_0001",
143
+ "target": "first_name_f409",
36
144
  "pii": true,
37
- "required": false,
145
+ "contactMapping": { "contactField": "FIRST_NAME" },
38
146
  "inputType": "STRING",
39
- "readOnly": false,
40
147
  "stringOptions": {
41
- "validation": {
42
- "format": "UNKNOWN_FORMAT",
43
- "enum": []
44
- },
148
+ "validation": {},
45
149
  "componentType": "TEXT_INPUT",
46
- "textInputOptions": {
47
- "label": "First name",
48
- "showLabel": true
49
- }
150
+ "textInputOptions": { "label": "First name", "showLabel": true }
50
151
  }
51
152
  }
52
153
  },
53
154
  {
54
- "id": "c1a2b3d4-0002-4a00-b000-000000000002",
55
- "hidden": false,
155
+ "id": "740e294e-6ee4-4cda-c902-823002064985",
56
156
  "identifier": "CONTACTS_LAST_NAME",
57
157
  "fieldType": "INPUT",
58
158
  "inputOptions": {
59
- "target": "last_name_0002",
159
+ "target": "last_name_b88c",
60
160
  "pii": true,
61
- "required": false,
161
+ "contactMapping": { "contactField": "LAST_NAME" },
62
162
  "inputType": "STRING",
63
- "readOnly": false,
64
163
  "stringOptions": {
65
- "validation": {
66
- "format": "UNKNOWN_FORMAT",
67
- "enum": []
68
- },
164
+ "validation": {},
69
165
  "componentType": "TEXT_INPUT",
70
- "textInputOptions": {
71
- "label": "Last name",
72
- "showLabel": true
73
- }
166
+ "textInputOptions": { "label": "Last name", "showLabel": true }
74
167
  }
75
168
  }
76
169
  },
77
170
  {
78
- "id": "c1a2b3d4-0003-4a00-b000-000000000003",
79
- "hidden": false,
171
+ "id": "19768973-65be-4a6f-b3de-57cfb1da48db",
80
172
  "identifier": "CONTACTS_EMAIL",
81
173
  "fieldType": "INPUT",
82
174
  "inputOptions": {
83
- "target": "email_0003",
175
+ "target": "email_673d",
84
176
  "pii": true,
85
177
  "required": true,
178
+ "contactMapping": { "contactField": "EMAIL", "emailInfo": { "tag": "UNTAGGED" } },
86
179
  "inputType": "STRING",
87
- "readOnly": false,
88
180
  "stringOptions": {
89
- "validation": {
90
- "format": "EMAIL",
91
- "enum": []
92
- },
181
+ "validation": { "format": "EMAIL" },
93
182
  "componentType": "TEXT_INPUT",
94
- "textInputOptions": {
95
- "label": "Email",
96
- "showLabel": true
97
- }
183
+ "textInputOptions": { "label": "Email", "showLabel": true }
98
184
  }
99
185
  }
100
186
  },
101
187
  {
102
- "id": "c1a2b3d4-0004-4a00-b000-000000000004",
103
- "hidden": false,
104
- "identifier": "TEXT_INPUT",
188
+ "id": "95d528a8-8f3d-4692-7363-44db1f96ca18",
189
+ "identifier": "CONTACTS_PHONE",
105
190
  "fieldType": "INPUT",
106
191
  "inputOptions": {
107
- "target": "message_0004",
108
- "pii": false,
109
- "required": false,
192
+ "target": "phone_6a4b",
193
+ "pii": true,
194
+ "contactMapping": { "contactField": "PHONE", "phoneInfo": { "tag": "UNTAGGED" } },
110
195
  "inputType": "STRING",
111
- "readOnly": false,
112
196
  "stringOptions": {
113
- "validation": {
114
- "format": "UNKNOWN_FORMAT",
115
- "enum": []
116
- },
197
+ "validation": { "format": "PHONE" },
198
+ "componentType": "PHONE_INPUT",
199
+ "phoneInputOptions": { "label": "Phone", "showLabel": true }
200
+ }
201
+ }
202
+ },
203
+ {
204
+ "id": "bbecbd37-ca52-4fa6-a92c-90e70aa4ec2a",
205
+ "identifier": "TEXT_AREA",
206
+ "fieldType": "INPUT",
207
+ "inputOptions": {
208
+ "target": "message_7634",
209
+ "inputType": "STRING",
210
+ "stringOptions": {
211
+ "validation": {},
117
212
  "componentType": "TEXT_INPUT",
118
- "textInputOptions": {
119
- "label": "Message",
120
- "showLabel": true
121
- }
213
+ "textInputOptions": { "label": "Your message", "showLabel": true }
122
214
  }
123
215
  }
124
216
  },
125
217
  {
126
- "id": "c1a2b3d4-0005-4a00-b000-000000000005",
127
- "hidden": false,
218
+ "id": "2e56791e-926e-48fd-37d0-0ad60a27736d",
128
219
  "identifier": "SUBMIT_BUTTON",
129
220
  "fieldType": "DISPLAY",
130
221
  "displayOptions": {
131
222
  "displayFieldType": "PAGE_NAVIGATION",
132
- "pageNavigationOptions": {
133
- "nextPageText": "Next",
134
- "previousPageText": "Back",
135
- "submitText": "Submit"
136
- }
223
+ "pageNavigationOptions": { "submitText": "Send" }
137
224
  }
138
225
  }
139
226
  ],
140
227
  "steps": [
141
228
  {
142
- "id": "d1e2f3a4-0001-4b00-c000-000000000001",
229
+ "id": "a26d12fc-ed7a-4b7f-90e4-994f3ad56e4b",
143
230
  "name": "Page 1",
144
- "hidden": false,
145
231
  "layout": {
146
232
  "large": {
147
233
  "items": [
148
- { "fieldId": "c1a2b3d4-0001-4a00-b000-000000000001", "row": 0, "column": 0, "width": 6, "height": 1 },
149
- { "fieldId": "c1a2b3d4-0002-4a00-b000-000000000002", "row": 0, "column": 6, "width": 6, "height": 1 },
150
- { "fieldId": "c1a2b3d4-0003-4a00-b000-000000000003", "row": 1, "column": 0, "width": 12, "height": 1 },
151
- { "fieldId": "c1a2b3d4-0004-4a00-b000-000000000004", "row": 2, "column": 0, "width": 12, "height": 1 },
152
- { "fieldId": "c1a2b3d4-0005-4a00-b000-000000000005", "row": 3, "column": 6, "width": 6, "height": 1 }
153
- ],
154
- "sections": []
234
+ { "fieldId": "d7665e98-a7c4-4829-c104-fb856883e043", "row": 0, "column": 0, "width": 6, "height": 1 },
235
+ { "fieldId": "740e294e-6ee4-4cda-c902-823002064985", "row": 0, "column": 6, "width": 6, "height": 1 },
236
+ { "fieldId": "19768973-65be-4a6f-b3de-57cfb1da48db", "row": 1, "column": 0, "width": 12, "height": 1 },
237
+ { "fieldId": "95d528a8-8f3d-4692-7363-44db1f96ca18", "row": 2, "column": 0, "width": 12, "height": 1 },
238
+ { "fieldId": "bbecbd37-ca52-4fa6-a92c-90e70aa4ec2a", "row": 3, "column": 0, "width": 12, "height": 1 },
239
+ { "fieldId": "2e56791e-926e-48fd-37d0-0ad60a27736d", "row": 4, "column": 6, "width": 6, "height": 1 }
240
+ ]
155
241
  }
156
242
  }
157
243
  }
158
244
  ],
159
- "postSubmissionTriggers": {
160
- "upsertContact": {
161
- "fieldsMapping": {
162
- "first_name_0001": { "contactField": "FIRST_NAME" },
163
- "last_name_0002": { "contactField": "LAST_NAME" },
164
- "email_0003": { "contactField": "EMAIL", "emailInfo": { "tag": "UNTAGGED" } }
165
- },
166
- "labels": []
167
- }
168
- },
169
245
  "submitSettings": {
170
246
  "submitSuccessAction": "THANK_YOU_MESSAGE",
171
247
  "thankYouMessageOptions": {
@@ -174,29 +250,13 @@ curl -X POST \
174
250
  "nodes": [
175
251
  {
176
252
  "type": "PARAGRAPH",
177
- "id": "ty1",
253
+ "id": "ctf1a20",
178
254
  "nodes": [
179
- {
180
- "type": "TEXT",
181
- "id": "",
182
- "nodes": [],
183
- "textData": {
184
- "text": "Thanks, we received your submission.",
185
- "decorations": []
186
- }
187
- }
255
+ { "type": "TEXT", "id": "", "nodes": [], "textData": { "text": "Thanks for reaching out. We will get back to you soon.", "decorations": [] } }
188
256
  ],
189
- "paragraphData": {
190
- "textStyle": { "textAlignment": "CENTER" }
191
- }
257
+ "paragraphData": { "textStyle": { "textAlignment": "CENTER" } }
192
258
  }
193
- ],
194
- "metadata": {
195
- "version": 1,
196
- "createdTimestamp": "2025-01-01T00:00:00.000Z",
197
- "updatedTimestamp": "2025-01-01T00:00:00.000Z",
198
- "id": "thank-you-msg-001"
199
- }
259
+ ]
200
260
  }
201
261
  }
202
262
  }
@@ -204,107 +264,135 @@ curl -X POST \
204
264
  }'
205
265
  ```
206
266
 
207
- The response includes the created form with its `id`. Store this ID to manage the form later.
267
+ Read `form.id` from the response — that is the `formId` to keep. Also read back `form.name`: **names are unique per namespace**, and a colliding name is silently saved as a numbered variation rather than erroring.
208
268
 
209
- Verify the form in the dashboard: `https://manage.wix.com/dashboard/{siteId}/forms`
269
+ > `spamFilterProtectionLevel` defaults to `ADVANCED`; set it only to change that.
210
270
 
211
- ## Key Details
271
+ **A `200` proves nothing. Always run STEP 3.** Every failure mode in § Silent breakers returns `200`.
212
272
 
213
- ### Field Configuration
273
+ ---
214
274
 
215
- - All `id` fields (for `formFields`, `steps`) and all `fieldId` references in the layout **must be valid UUIDs**. Generate fresh UUIDs for each form you create — do not reuse the example UUIDs above.
216
- - Each field needs a unique `id` and a unique `target` value. The `target` is used to map submissions to contact fields.
217
- - **CRITICAL: The `identifier` must be a recognized Wix value.** Custom identifiers like `"product_name"` or `"color_preference"` will cause the field to be silently dropped from the form — no error is thrown. For any generic/custom text field, use `"TEXT_INPUT"` as the identifier and set the display name via the `label` property in `textInputOptions`.
218
- - For plain text fields, use `"format": "UNKNOWN_FORMAT"`. For email fields, use `"format": "EMAIL"`. For phone fields, use `"format": "PHONE"`. Valid format values: `UNKNOWN_FORMAT`, `DATE`, `TIME`, `DATE_TIME`, `EMAIL`, `URL`, `UUID`, `PHONE`, `URI`, `HOSTNAME`, `COLOR_HEX`, `CURRENCY`, `LANGUAGE`, `DATE_OPTIONAL_TIME`.
219
- - The submit button is a `DISPLAY` field with `identifier: "SUBMIT_BUTTON"`.
220
- - **Build the complete form in one call — do not create throwaway "test" forms to probe field shapes.** A site has a **low form cap (~4 forms)**; iterative probing hits the cap (`maximum number of forms reached`), forcing you to `GET` the form list and `DELETE` the test forms before the real create can succeed. Assemble all fields (including any RADIO_GROUP/DROPDOWN per § "Choice fields") and POST once.
275
+ ## STEP 3: Verify the form persisted (mandatory)
221
276
 
222
- ### Field Types Reference
277
+ **1 · List it back** and diff against what you sent:
223
278
 
224
- | Identifier | componentType | format | Use case |
225
- |---|---|---|---|
226
- | `TEXT_INPUT` | `TEXT_INPUT` | `UNKNOWN_FORMAT` | Generic single-line text (use `label` for display name) |
227
- | `CONTACTS_FIRST_NAME` | `TEXT_INPUT` | `UNKNOWN_FORMAT` | Contact first name |
228
- | `CONTACTS_LAST_NAME` | `TEXT_INPUT` | `UNKNOWN_FORMAT` | Contact last name |
229
- | `CONTACTS_EMAIL` | `TEXT_INPUT` | `EMAIL` | Contact email |
230
- | `CONTACTS_PHONE` | `TEXT_INPUT` | `PHONE` | Contact phone |
231
- | `SUBMIT_BUTTON` | N/A (`DISPLAY` field) | N/A | Submit button |
232
- | `TEXT_INPUT` | `RADIO_GROUP` | `UNKNOWN_FORMAT` | Single-choice from a fixed list (radio buttons) — see § "Choice fields" |
233
- | `TEXT_INPUT` | `DROPDOWN` | `UNKNOWN_FORMAT` | Single-choice from a fixed list (dropdown) — same shape as RADIO_GROUP |
279
+ ```bash
280
+ curl -X GET \
281
+ 'https://www.wixapis.com/form-schema-service/v4/forms?namespace=wix.form_app.form&formIds=<formId>' \
282
+ -H 'Authorization: <AUTH>'
283
+ ```
234
284
 
235
- > **Note:** `LONG_TEXT_INPUT` is not supported as a `componentType` via REST — it throws `INVALID_ARGUMENT`. Use `TEXT_INPUT` for all text fields.
285
+ Confirm the `id` appears, that `formFields[]` covers **every** field you sent, that `steps` is non-empty and **places every field**, and that each field's `inputOptions.required` matches what you sent — a misplaced `required` is dropped silently and this read-back is the only signal.
236
286
 
237
- ### Choice fields (RADIO_GROUP / DROPDOWN)
287
+ **Also assert every returned `formFields[].identifier` is a value from [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Field types.** An invented identifier survives this read-back intact — it is stored and returned like any other — so nothing else here flags it, and the field is invisible to the owner in the editor. This is a pure string comparison against the table; it needs no extra call.
238
288
 
239
- A single-choice field (radio buttons or a dropdown — e.g. an RSVP "Will you attend?") is a **`STRING` input field**, not a separate field type. It uses `identifier: "TEXT_INPUT"`, `inputType: "STRING"`, and sets `componentType` to `RADIO_GROUP` (or `DROPDOWN`) **inside `stringOptions`**. Two things must agree or the field breaks:
289
+ **2 · Verify the dashboard and Editor will actually render it:**
240
290
 
241
- 1. **`stringOptions.validation.enum`** must list every option `value` (an empty `enum` is for free-text only).
242
- 2. **`stringOptions.radioGroupOptions.options[]`** carries the rendered choices — each option needs its own **UUID `id`**, a `value`, and a `label`. (For `DROPDOWN`, use `dropdownOptions` with the same `{id, value, label}` shape.)
291
+ ```bash
292
+ curl -X GET \
293
+ 'https://www.wixapis.com/form-schema-service/v4/forms/<formId>/summary' \
294
+ -H 'Authorization: <AUTH>'
295
+ ```
243
296
 
244
- > **CRITICAL — silent fallback to TEXT_INPUT.** If `radioGroupOptions` is missing/malformed (wrong key like `choices` instead of `options`, an option missing its `id`, or an empty `validation.enum`), the API does **not** error — it silently creates the field as a plain `TEXT_INPUT`. If a choice field renders as a text box, this is why. Build it correctly on the first call; do not probe.
297
+ **Assert `formSummary.fields` is NON-EMPTY, with a count equal to every input field you sent** (`formFields[]` minus `SUBMIT_BUTTON` and any other `DISPLAY` field). A 5-input form returns all 5 — **including non-contact dropdowns and long-answer fields**, so do not expect only the contact-mapped ones.
245
298
 
246
- ```json
247
- {
248
- "id": "a1b2c3d4-1002-4e00-8001-000000000002",
249
- "hidden": false,
250
- "identifier": "TEXT_INPUT",
251
- "fieldType": "INPUT",
252
- "inputOptions": {
253
- "target": "attending_0002",
254
- "pii": false,
255
- "required": true,
256
- "inputType": "STRING",
257
- "readOnly": false,
258
- "stringOptions": {
259
- "validation": {
260
- "format": "UNKNOWN_FORMAT",
261
- "enum": ["Joyfully accepts", "Regretfully declines"]
262
- },
263
- "componentType": "RADIO_GROUP",
264
- "radioGroupOptions": {
265
- "label": "Will you attend?",
266
- "showLabel": true,
267
- "numberOfColumns": "ONE",
268
- "options": [
269
- { "id": "c3d4e5f6-3001-4a00-8001-000000000001", "value": "Joyfully accepts", "label": "Joyfully accepts" },
270
- { "id": "c3d4e5f6-3001-4a00-8001-000000000002", "value": "Regretfully declines", "label": "Regretfully declines" }
271
- ]
272
- }
273
- }
274
- }
275
- }
299
+ **This is the dashboard-truth check for *placement*.** `summary.fields: []`, or a count short of your inputs, means the owner opens the Editor's form picker and sees an **empty form**. **Do not report success** — fix the layout placement or the GUID casing, then re-verify.
300
+
301
+ > **⚠️ Do not lean on this check to catch a bad `identifier`.** Whether an unrecognized identifier is omitted from `formSummary.fields` is **unverified** — it may well be counted here and still be unrenderable in the editor. Check `identifier`s explicitly in step 1; treat this step as covering placement only.
302
+
303
+ **3 · If the form has an ARRAY field (`CHECKBOX_GROUP` / `TAGS` / `IMAGE_CHOICE`), send one real submission.** Checks 1–2 both pass on a malformed `arrayOptions.validation.items` while every submission `400`s:
304
+
305
+ ```bash
306
+ curl -X POST \
307
+ 'https://www.wixapis.com/form-submission-service/v4/submissions' \
308
+ -H 'Content-Type: application/json' \
309
+ -H 'Authorization: <AUTH>' \
310
+ -d '{ "submission": { "formId": "<formId>", "submissions": { "email_673d": "test@example.com", "multi_choice_a1b2": ["Option 1"] } } }'
276
311
  ```
277
312
 
278
- `numberOfColumns` is a string enum (`"ONE"`, `"TWO"`, `"THREE"`) controlling the radio layout — omit it and the field still works (defaults to one column).
313
+ Assert `200`, not `400 SUBMISSION_VALIDATION`. Then delete the test submission (`DELETE https://www.wixapis.com/form-submission-service/v4/submissions/{submissionId}`) so the owner's dashboard stays clean.
279
314
 
280
- ### Layout
315
+ **4 · Hand back the dashboard links** (see [Forms Dashboard Navigation](./forms-dashboard-navigation.md)):
281
316
 
282
- The `steps[].layout.large.items` array controls how fields are positioned:
283
- - `row` and `column` set the position (0-based grid)
284
- - `width` sets the column span (max 12 for full width, 6 for half)
285
- - `height` is typically 1
317
+ ```
318
+ Edit it here: https://manage.wix.com/dashboard/{metaSiteId}/wix-forms/form/{formId}
319
+ See submissions: https://manage.wix.com/dashboard/{metaSiteId}/wix-forms/form/{formId}/submissions
320
+ ```
321
+
322
+ ---
286
323
 
287
- ### Post-Submission Triggers
324
+ ## Changing a form that already exists
325
+
326
+ Adding, relabelling, re-requiring, reordering or retiring a field on a live form is a `PATCH`, not a second create — see **[Update Form](./update-form.md)**. Never delete-and-recreate: the `formId` is what everything downstream holds.
327
+
328
+ ---
288
329
 
289
- The `postSubmissionTriggers.upsertContact` object maps form field targets to contact fields, so each submission automatically creates or updates a contact. The `fieldsMapping` keys must match the `target` values from the form fields.
330
+ ## Plan caps — surface the choice, never engineer around it
290
331
 
291
- ### Prerequisites
332
+ These limits return a real `400` on create. **Do not work around any of them. Put the choice to the user — reduce, or upgrade — with the MSID and the upgrade link, wait for their answer, then create and verify.**
292
333
 
293
- The Wix Forms app (appDefId: `14ce1214-b278-a7e4-1373-00cebd1bef7c`) must be installed on the site. It is usually pre-installed, but if the API returns a "missing installed app" error, install it first using the [Install Wix Apps](../app-installation/install-wix-apps.md) recipe.
334
+ - **Field count** — **two different caps count two different things, so check which error you got:**
335
+ - `Field count reached its limit of N` — the **premium** cap, enforced by the Wix Forms app from
336
+ the site's plan. It counts **`INPUT` fields only** — display elements and the `SUBMIT_BUTTON` do
337
+ **not** count against it. This is the one free sites hit at 10.
338
+ - `FORM_FIELDS_COUNT_EXCEEDED` — the schema-service cap (`providers-config`'s `maxFields`), which
339
+ counts **all** fields **including** display elements and the submit button.
340
+
341
+ For either: **do NOT split the form across several schemas** to dodge it — that trades one submission record for several and consumes more of the site's form allowance. Reduce the field count, or upgrade.
342
+ - **Step count** (`Steps count reached its limit of N`) — the premium cap on `steps.length`. Collapse the form into fewer pages, or upgrade. There is **no schema-service equivalent**, so this error never appears in the Create Form error table.
343
+ - **Condition count** (`formRules.length`) — also premium-capped, and also absent from the Create Form error table.
344
+ - **Form count** (`NAMESPACE_FORMS_COUNT_EXCEEDED`, `NAMESPACE_DELETED_FORMS_COUNT_EXCEEDED`, or `FORM_SIZE_EXCEEDED` for a single oversized schema) — the site hit its total-form (or trash-bin) cap. Independently of the plan, `formFields` has a hard ceiling of 500 items. Upgrade, or free a slot per STEP 0 · 3 — deleting only what is clearly install sample data.
345
+ - **Premium fields** — file upload, signature and all four payment fields need a **Core plan or higher**; payment fields additionally need **Wix eCommerce**. Appointment needs **Wix Meetings**; the service pickers need **Wix Services**. A create including one of these on a site without the plan or app **fails**. **Do NOT suggest inlining files as base64** — it stores no real file, gives the owner nothing usable, and blows past submission size limits. Drop the field, or upgrade.
346
+
347
+ > **⚠️ A plan cap is a hard block on the run, not a "note it and continue" precondition.** The schema does not exist, so neither does its `formId` or its field `target`s. Nothing that depends on the form — a frontend binding, an automation, a submissions view — can be built "in the meantime" without guessing.
348
+
349
+ ---
350
+
351
+ ## Automations on form submission
352
+
353
+ To auto-respond to submissions, the automation's trigger belongs to the **app that owns the form** — and the two Forms apps have different trigger keys:
354
+
355
+ | App | `appId` | Trigger key for "Form submitted" |
356
+ |---|---|---|
357
+ | **Wix Forms (New)** | `225dd912-7dea-4738-8688-4b8c6955ffc2` | `wix_form_app-form_submitted` |
358
+
359
+ **The app and the key must be the same generation.** A form created on Form Schemas v4 belongs to the New app, so its automation must use `wix_form_app-form_submitted` **and** the New app must be the installed one. `FAILED_PRECONDITION: "Forms app is not installed on the site"` on an automation create, on a site where Wix Forms visibly *is* installed, means the **Old** app is installed and the New one isn't — fix that at STEP 0 · 1, not by swapping trigger keys.
360
+
361
+ Confirm the pair against the site rather than typing it from memory: [Query Triggers](https://dev.wix.com/docs/api-reference/business-management/automations/triggers/trigger-catalog/query-triggers) filtered by `appId`, or [Get Trigger By App Id And Key](https://dev.wix.com/docs/api-reference/business-management/automations/triggers/trigger-catalog/get-trigger-by-app-id-and-key).
362
+
363
+ ---
294
364
 
295
365
  ## Troubleshooting
296
366
 
297
- | Error | Cause | Fix |
367
+ | Error / symptom | Cause | Fix |
298
368
  |---|---|---|
299
- | `Unrecognized value passed for enum` | Invalid `componentType` value (e.g., `LONG_TEXT_INPUT`) | Use only `componentType` values from the schema: `TEXT_INPUT`, `RADIO_GROUP`, `DROPDOWN`, `DATE_TIME`, `PHONE_INPUT`, `DATE_INPUT`, `TIME_INPUT`, `DATE_PICKER`, `PASSWORD` |
300
- | Field silently missing from created form | Custom `identifier` value (e.g., `"product_name"`) | Use a recognized identifier like `TEXT_INPUT` and set display name via `label` |
301
- | Choice field rendered as a plain text box | `radioGroupOptions`/`dropdownOptions` malformed (wrong key, option missing `id`, empty `validation.enum`) — API silently falls back to `TEXT_INPUT` | Match the § "Choice fields" shape exactly: `componentType` in `stringOptions`, `options[]` each with a UUID `id`, and `validation.enum` listing all option values |
302
- | `maximum number of forms reached` / form-cap error | Sites cap at ~4 forms; reached by creating throwaway test forms | `GET form-schema-service/v4/forms` then `DELETE` the unwanted forms; build the real form in one call (don't probe) |
303
- | `Permissions for given namespace not found` | `wix.form_app.form` namespace not active | Ensure the Wix Forms app is installed; try creating a form through the UI first to activate the namespace |
304
- | `missing installed app` | Wix Forms app not installed | Install app `14ce1214-b278-a7e4-1373-00cebd1bef7c` via the [Install Wix Apps](../app-installation/install-wix-apps.md) recipe |
369
+ | `UNSUPPORTED_FORM_NAMESPACE`, or `Permissions for given namespace not found` | Wix Forms **(New)** not installed, or a namespace other than `wix.form_app.form` | Install `225dd912-7dea-4738-8688-4b8c6955ffc2`; use `wix.form_app.form` |
370
+ | Form reads back fine over the API but is **invisible** in the Forms dashboard and Editor | Created under a non-dashboard namespace — typically the non-existent `wix.form_platform.form` | Re-create under `wix.form_app.form`. A namespace query returning **0 results is not proof it is unusable** — it may simply be empty |
371
+ | Automation create fails `FAILED_PRECONDITION: Forms app is not installed` although Wix Forms is installed | The **Old** Forms app (`14ce1214-…`) is installed; the New app's trigger `wix_form_app-form_submitted` needs `225dd912-…` | Install `225dd912-…` — do **not** try to fix it by swapping in the Old app's key `wix_forms-form_submit`. See § Automations on form submission |
372
+ | Form appears in the Editor's form picker but renders **empty** | Fields not placed in `steps[].layout`, or an **uppercase** `id` whose stored lowercase form no longer matches `fieldId` | Place every field (incl. `SUBMIT_BUTTON`); use lowercase GUIDs on both sides; re-verify with `/summary` |
373
+ | Field is returned by the API and accepts submissions, but the owner **cannot see or edit it in the Wix Forms editor** (a whole form of them opens **empty**) | Invented `identifier` (e.g. `"product_name"`) — accepted and stored, but unrecognized by the editor | Use a value from [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Field types; put the user's wording in the component's `label`. Assert identifiers in STEP 3 · 1 — no other check catches this |
374
+ | Field vanishes entirely from the created form | `componentType` not valid for the field's `inputType` — the server builds no view for it | Match the `inputType` / `componentType` pair in [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Field types |
375
+ | Choice field renders as a plain text box, and reads back with `textInputOptions` | `identifier` was `TEXT_INPUT` rather than `DROPDOWN` / `RADIO_GROUP` — `componentType` alone doesn't route the renderer | Set `identifier` to the choice kind and re-send the field. Deleting and re-adding it with the same `identifier` reproduces the fallback |
376
+ | `options[N].id is not a valid GUID` on a create or `PATCH` | Option `id`s were readable slugs (`tech-opt-1`) or omitted — each needs its own lowercase GUID, client-generated | Generate one GUID per option (`uuidgen`); the field's own `id` being a GUID is not enough |
377
+ | Choice field renders as a plain text box | `radioGroupOptions` / `dropdownOptions` malformed — wrong key (`choices` instead of `options`), an option missing its GUID `id`, or an empty `validation.enum` | Match [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Choice fields exactly: `componentType` inside the `inputType` block, every option with a lowercase GUID `id`, `validation.enum` listing all values |
378
+ | Form lists and summarizes fine, but **every** submission returns `400` | ARRAY field with malformed `arrayOptions.validation.items` — missing `itemType`, or empty/omitted `items` | Set both `items.itemType` and `items.stringOptions.enum`; prove it with STEP 3 · 3 |
379
+ | Every field reads back `required: false` | `required` placed inside `validation` instead of `inputOptions` | Move it to `inputOptions.required`; fix with a `PATCH` ([Update Form](./update-form.md)) rather than re-creating |
380
+ | Contacts are never created or updated on submission | Used `postSubmissionTriggers.upsertContact` (absent from the current v4 contract — it configures nothing) | Set per-field `inputOptions.contactMapping.contactField` + `pii: true` |
381
+ | `400 namespace has size 0, expected 10 or more` / `namespace must not be empty` on a read | The `namespace` **query parameter** was omitted from `GET .../v4/forms` (or from the `query` filter) — it is required on every read, and the violation naming a field makes it look like a body problem | Add `?namespace=wix.form_app.form` to the read; leave the payload alone |
382
+ | `Unrecognized value passed for enum` | Invented `componentType` (e.g. `LONG_TEXT_INPUT`) | Use the `componentType` from [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) § Field types — a long answer is `identifier: TEXT_AREA` with `componentType: TEXT_INPUT` |
383
+ | `Field count reached its limit of N` / `FORM_FIELDS_COUNT_EXCEEDED` | The site's premium-plan field cap, enforced by the Wix Forms app — unrelated to the app-declared `maxFields` in `providers-config` | § Plan caps: reduce or upgrade. Never split across schemas |
384
+ | `Steps count reached its limit of N` | Plan's per-form step cap | Collapse to fewer pages, or upgrade |
385
+ | `NAMESPACE_FORMS_COUNT_EXCEEDED` | Site hit its total-form cap | § Plan caps: upgrade, or free a slot (STEP 0 · 3) |
386
+ | `DUPLICATED_FIELD_TARGETS` / `DUPLICATED_FIELD_IDS` / `MISSING_FIELD_TARGETS` | Reused `target` or `id`, or omitted `target` | Give every field a unique lowercase GUID `id` and a unique `snake_case` `target` |
387
+ | Form saved under a different name than requested | Names are unique per namespace; a collision saves a numbered variation | Read `name` from the response and report the actual name |
388
+
389
+ ---
305
390
 
306
391
  ## Related Documentation
307
392
 
308
- - [Form Schemas API Introduction](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/introduction)
309
- - [Create Form API Reference](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/create-form)
310
- - [Form Submissions API](https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/introduction)
393
+ - [About Form Fields](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields) — the authoritative field-composition guide
394
+ - [Create Form](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/create-form) — complete request examples for a dozen form types
395
+ - [Form object](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/form-object) · [Update Form](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/update-form) · [Get Form Summary](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/get-form-summary) · [List Forms Providers Configs](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/list-forms-providers-configs)
396
+ - [Form Schemas API Introduction](https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/introduction) — including the namespaces of apps made by Wix
397
+ - [Form Submissions API](https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/introduction) · [About Submission Values](https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/about-submission-values)
398
+ - [Forms Dashboard Navigation](./forms-dashboard-navigation.md) · [Install Wix Apps](../app-installation/install-wix-apps.md)