← Files WixARCHIVED FILE
skills/wix-headless-templates/forms/rest/submissions.ts
4.35 KB · Oct 8, 2026 · 12:02 UTC
// Submissions over REST — the twin of app/wix/forms/submissions.ts. Same exports, same
// SubmissionDto; the rules come from submissions-core (the SAME file the SDK transport uses,
// deployed flat next to this one). Every call runs with the visitor token: `CreateSubmission` is
// listed under an owner scope and still returns 200 to an anonymous visitor, so a published site
// can submit its own forms. Submissions are write-only from a visitor — the resolved create IS
// the confirmation. A rejection throws a WixApiError whose `details` block `submissionErrors` and
// `formLevelError` map onto the page; let it throw.
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/create-submission.md
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/get-media-upload-url.md
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
import { wixRequest } from "./client.js";
import type { Raw } from "./forms-core.js";
import {
SUBMITTED_OK,
formLevelError,
normalizePhone,
normalizeUrl,
putUpload,
redirectSessionBody,
redirectSessionUrl,
submissionErrors,
toSubmission,
toSubmissionValues,
uploadFilesWith,
uploadMimeType,
} from "./submissions-core.js";
import type { FormFieldDto, FormValues, SubmissionDto, UploadedFile } from "./types.js";
export { SUBMITTED_OK, formLevelError, normalizePhone, normalizeUrl, submissionErrors, toSubmissionValues };
const SUBMISSIONS = "/form-submission-service/v4/submissions";
/**
* Upload one File and return the value to submit for its field ({ fileId, displayName, fileType, url }).
* POST /form-submission-service/v4/submissions/media-upload-url { formId, filename, mimeType } → { uploadUrl }
* then PUT the bytes to that URL with plain fetch (submissions-core.putUpload) and read its `file` back.
*/
export async function uploadFile(formId: string, file: File): Promise<UploadedFile> {
const mimeType = uploadMimeType(file);
const res = await wixRequest<Raw>(`${SUBMISSIONS}/media-upload-url`, { body: { formId, filename: file.name, mimeType } });
const uploadUrl: string | undefined = res?.uploadUrl;
if (!uploadUrl) throw new Error(`forms: no upload URL for "${file.name}".`);
return putUpload(uploadUrl, file, mimeType);
}
/** Upload every File in the form's values; returns a copy with each file field holding its uploaded entries. */
export async function uploadFiles(formId: string, fields: FormFieldDto[], values: FormValues): Promise<FormValues> {
return uploadFilesWith(uploadFile, formId, fields, values);
}
export interface CreateSubmissionOptions {
/** A captcha token when the site runs a captcha widget; the server answers INVALID_CAPTCHA when it wants one and got none. */
captchaToken?: string;
}
/**
* Create the submission — the write, and the only confirmation there is. `values` is
* toSubmissionValues(fields, values): every key a field `target`, every value in that field's
* shape. A 400 carries details.validationError.fieldViolations[].data.errors[] (errorPath,
* errorType, useCustomErrorMessage) or details.applicationError.code on the thrown WixApiError.
* POST /form-submission-service/v4/submissions { submission: { formId, submissions }, captchaToken? } → { submission: { id, status, orderDetails? } }
*/
export async function createSubmission(
formId: string,
values: Record<string, unknown>,
{ captchaToken }: CreateSubmissionOptions = {},
): Promise<SubmissionDto> {
const created = await wixRequest<Raw>(SUBMISSIONS, {
body: { submission: { formId, submissions: values }, ...(captchaToken ? { captchaToken } : {}) },
});
return toSubmission(created);
}
/**
* The Wix-hosted checkout for a `PAYMENT_WAITING` submission — navigate the FULL document to the
* URL. `origin` must be the site's real https origin (window.location.origin) as registered on
* the OAuth app, so the checkout can send the visitor back.
* POST /headless/v1/redirect-session { ecomCheckout: { checkoutId }, callbacks: { postFlowUrl, thankYouPageUrl } } → { redirectSession: { fullUrl } }
*/
export async function checkoutUrl(checkoutId: string, origin: string = typeof window !== "undefined" ? window.location.origin : ""): Promise<string> {
const res = await wixRequest<Raw>("/headless/v1/redirect-session", { body: redirectSessionBody(checkoutId, origin) });
return redirectSessionUrl(res);
}
SHA-256: a658c2b447caffcd8d730408d2c091b9d56ce3b2a1fa02ff79c4c63d77ba5b30