Formulieren
Formulieren
Hoe een site het formulier van een merchant laadt, de stappen en regels rendert, bestanden uploadt en inzendingen vanaf de eigen server verstuurt, met veldtypes, regelsemantiek, spamcontroles, keys en limieten.
Bekijk als MarkdownMet de Formulieren-module bouwt een merchant contact-, offerte- en intakeformulieren in het client-dashboard en ontvangt de antwoorden in een inbox in het dashboard. Je site zet de vragen niet hard in de code: hij laadt het gepubliceerde formulier via zijn key, rendert het en stuurt de antwoorden van de bezoeker terug. Borkol controleert de antwoorden opnieuw op de server, slaat de inzending op, nummert haar, mailt de merchant en de bezoeker en verstuurt een webhook.
Alle vier de endpoints staan onder /v1/api/forms, vereisen dat de module forms is ingeschakeld voor de winkel (anders 403 {"error":"Module not enabled: forms"}) en vallen onder de winkelbrede rate limit van 120 requests per minuut (zie Errors en rate limits).
| Endpoint | Scope | Doel |
|---|---|---|
GET /v1/api/forms/{key} | forms:read | Laad de gepubliceerde versie van een actief formulier in één taal. Zie Een formulier laden. |
GET /v1/api/forms/address-lookup | forms:read | Zoek straat en plaats bij een postcode en huisnummer, voor een address-veld. Zie Adres opzoeken. |
POST /v1/api/forms/{key}/uploads | forms:write | Sla één bestand op voor een file-veld en krijg een token voor de inzending. Zie Uploads. |
POST /v1/api/forms/{key}/submissions | forms:write | Verstuur de antwoorden van één bezoeker. Zie Inzendingen. |
Houd forms:write-sleutels op je server
Met een sleutel met forms:write kan iemand bestanden uploaden en inzendingen versturen namens de merchant, en elke inzending verstuurt mail. Zet een forms:write-sleutel nooit in browsercode, in een bundel van een mobiele app of op een andere plek die een bezoeker kan lezen. Laad het formulier, upload bestanden en verstuur vanaf je server (een route handler, een server action, een backend-endpoint) en laat de browser alleen met je eigen server praten. Eén sleutel op je server met zowel forms:read als forms:write (bijvoorbeeld in een omgevingsvariabele BORKOL_FORMS_KEY) is prima, want je server doet alle drie de calls. Een aparte sleutel met alleen forms:read om formulieren te laden is optioneel.
Omdat elk request dan van het adres van je server komt, geef je het IP-adres van de bezoeker mee in de header X-Borkol-Visitor-Ip op de upload- en submit-requests. Borkol past de limieten per uur toe per bezoekersadres (zie Bezoekers-IP en de limiet per uur).
Het verloop
- Laden:
GET /v1/api/forms/{key}?locale=nlop je server. Cache het resultaat en ververs het wanneer de webhookform.publishedbinnenkomt. - Renderen: toon de stappen op volgorde. Toon een stap of veld alleen zolang de regel in
show_ifklopt (zie Regels). Vul eventueel antwoorden vooraf in vanuit de pagina-URL (zie Vooraf invullen vanuit de URL). - Controleren: controleer elke stap voordat de bezoeker verdergaat, met dezelfde regels als de server. Het pakket
@borkol/formsimplementeert ze (zie Het pakket @borkol/forms). - Uploaden: elk bestand dat de bezoeker kiest voor een
file-veld:POST /v1/api/forms/{key}/uploadsvanaf je server geeft een token terug (zie Uploads). - Spamcontrole: geeft het formulier
settings.turnstile_site_keyterug, toon dan de widget van Cloudflare Turnstile en stuur het token mee (zie Turnstile). - Versturen:
POST /v1/api/forms/{key}/submissionsvanaf je server, met de antwoorden, de uploadtokens, het Turnstile-token, de taal van de bezoeker, de paginagegevens en de header met het bezoekers-IP. - Afronden: bij
201toon jethank_you.messageof stuur je de bezoeker naarthank_you.redirect_url. Bij422toon je elke fout bij de juiste vraag en reset je de Turnstile-widget. Bij429vraag je de bezoeker het later opnieuw te proberen.
Formulieren, versies en status
Een formulier heeft een key, een naam, een status en genummerde versies.
- De key kiest de merchant: kleine letters, cijfers en koppeltekens, beginnend met een letter, maximaal 64 tekens (bijvoorbeeld
quote-request). Hij is uniek per winkel en kan niet meer veranderen zodra het formulier is gepubliceerd. - Elke keer dat de merchant op Publiceren klikt, wordt het huidige concept een nieuwe, bevroren versie (1, 2, 3, ...). De publieke API levert altijd alleen de nieuwste gepubliceerde versie; niet-gepubliceerde wijzigingen bereiken je site nooit.
- Een formulier is
draft,activeofarchived. Alleen een actief formulier dat minstens één keer is gepubliceerd kun je laden en versturen; al het andere geeft404. Publiceren activeert een formulier niet, en activeren of archiveren publiceert niets. - Elke inzending onthoudt tegen welke versie ze is ingevuld, zodat de inbox van de merchant de vragen en optielabels blijft tonen die de bezoeker zag, ook als latere versies die hebben veranderd.
Definitie: stappen en velden
De inhoud van een formulier is een lijst stappen, elk met een lijst velden. Een formulier heeft maximaal 20 stappen en in totaal 150 invoervelden (heading en paragraph tellen niet mee). Keys van stappen en velden zijn uniek binnen het formulier en voldoen aan /^[a-z][a-z0-9_]*$/ (maximaal 50 tekens). Antwoorden worden verstuurd en opgeslagen onder de key van het veld.
Een stap:
| Veld | Type | Beschrijving |
|---|---|---|
key | string | Key van de stap. |
title | string | Titel van de stap in de gevraagde taal. |
show_if | object of null | De regel die bepaalt of de stap wordt getoond; null als hij altijd wordt getoond. |
fields | array | De velden van de stap, op volgorde. |
Een veld:
| Veld | Type | Beschrijving |
|---|---|---|
key | string | Key van het veld; het antwoord wordt onder deze key verstuurd. |
kind | string | Een van de types hieronder. |
label | string | De vraag. Bij heading en paragraph is het de tekst die je toont; bij consent de tekst naast het vinkje; bij hidden de naam die de merchant in de inbox ziet (niet renderen). |
help | string of null | Hulptekst onder de vraag. |
placeholder | string of null | Placeholder van het invoerveld. |
required | boolean | Of een zichtbaar veld moet worden beantwoord. Behandel een ontbrekende waarde als false. |
show_if | object of null | De regel van het veld; null als het altijd wordt getoond (binnen een zichtbare stap). |
config | object | Instellingen per type, {} als er geen zijn. |
Elke tekst is al teruggebracht tot één taal (zie Talen).
Veldtypes
Invoertypes verzamelen een antwoord; heading en paragraph tonen alleen tekst en worden nooit verstuurd.
| Type | config | Antwoordwaarde |
|---|---|---|
text | prefill? | string, maximaal 255 tekens. |
textarea | prefill? | string, maximaal 10000 tekens. |
number | prefill? | JSON-getal (geheel of decimaal). 0 telt als antwoord. |
email | prefill? | string: een geldig e-mailadres, maximaal 255 tekens. |
phone | prefill? | string: 5 tot 30 tekens met cijfers, spaties en ()-., met een optionele + vooraan. |
date | prefill? | string YYYY-MM-DD. |
select | options (lijst van strings), prefill? | string: een van options. De optiestrings worden getoond zoals ze zijn geschreven, in elke taal. |
link | prefill? | object {url, label?}: url maximaal 2000 tekens en beginnend met https://, http://, / (root-relatief), mailto: of tel:; label optioneel, maximaal 200 tekens. |
choice | options, multiple?, min?, max?, prefill? | Eén optie-value (string), of met multiple: true een lijst optiewaarden (dubbele verwijderd), minstens min en hoogstens max items als die zijn ingesteld. |
consent | {} | true. Verplicht betekent dat het vakje moet zijn aangevinkt. |
hidden | source?: query (standaard) of site | string, maximaal 500 tekens. Wordt nooit gerenderd; zie Verborgen velden. |
file | accept (image, pdf of beide), max_files (1 tot 10), max_mb (1 tot 20) | Een lijst uploadtokens, verstuurd in files, niet in answers; zie de opmerking hieronder. |
address | country (ISO 3166-1 alpha-2, bijvoorbeeld NL), lookup?, service_area? | object {postal_code, house_number, addition?, street, city, lat?, lon?}; zie de opmerking over adresvelden hieronder. |
heading | {} | Geen. Toon label als kop. |
paragraph | {} | Geen. Toon label als tekst (maximaal 5000 tekens, platte tekst met regeleinden). |
Een choice-optie is {value, label, description, icon}: value is wat je verstuurt (maximaal 100 tekens), label de tekst van de optie, description een optionele tweede regel (string of null), icon een optionele iconnaam (/^[a-z][a-z0-9-]{0,39}$/) of null. Een choice-veld heeft 1 tot 30 opties; een select-veld heeft 1 tot 50 optiestrings van maximaal 100 tekens.
Opties uit een collectie. Een choice-veld kan zijn opties uit een objectcollectie halen in plaats van uit een vaste lijst. Zijn config bevat dan options_source in plaats van een met de hand geschreven lijst options:
{
"multiple": false,
"options_source": {
"object_type": "services",
"label": "title",
"description": "summary",
"sort": "position",
"limit": 30
}
}| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
object_type | string | Ja | De key van een objecttype van de winkel. |
label | string | Ja | De key van een tekstproperty die als optielabel wordt gebruikt. |
description | string of null | Nee | De key van een tekstproperty die als optiebeschrijving wordt gebruikt. |
sort | string of null | Nee | De key van een getalproperty. Opties staan hierop oplopend gesorteerd, records zonder waarde als laatste; zonder sort volgen ze de aanmaakvolgorde. |
limit | integer | Nee | Hoeveel opties worden aangeboden, 1 tot 100. Standaard 30. |
Bij GET /v1/api/forms/{key} heeft de server de opties al ingevuld: config.options bevat de huidige opties in de gebruikelijke vorm {value, label, description, icon} (icon is null) en config.options_source blijft in het object staan. Render options en negeer options_source. De opties zijn de publiek zichtbare records van het type (concepten en ingeplande records blijven buiten beschouwing). value is het adres (de slug) van het record in de standaardtaal van de winkel als het type adressen heeft, anders het record-id; een record zonder adres in de standaardtaal wordt weggelaten. label en description worden gelezen in de gevraagde taal, met terugval op de standaardtaal. Ze worden bij elk request opgezocht, dus de opties volgen de collectie: ververs een gecachet formulier wanneer een object_record.published-, object_record.updated-, object_record.unpublished- of object_record.deleted-webhook binnenkomt. Een inzending wordt gecontroleerd tegen de opties op dat moment, dus een antwoord dat geen optie meer is geeft Choose one of the options. Met multiple: true zijn min en max hoogstens limit. Een veld heeft options of options_source, nooit allebei.
Een consent-label kan één link bevatten in markdown, [privacybeleid](https://example.com/privacy), altijd https://. Render die als link.
Bestandsvelden. Een file-veld beantwoord je in twee stappen. Upload eerst elk bestand met POST /v1/api/forms/{key}/uploads (multipart field en file); dat controleert het type aan de hand van de inhoud van het bestand en de grootte tegen max_mb, en geeft een token terug. Stuur de tokens daarna mee in het object files van de inzending, veld-key naar een lijst tokens (maximaal max_files), nooit in answers: een waarde die je voor een bestandsveld in answers stuurt, wordt genegeerd. Een token is 24 uur geldig, hoort bij één formulier en één veld, en werkt voor maar één inzending. Zie Uploads.
Adresvelden. Een address-veld beantwoord je in answers met één object. postal_code, house_number, street en city zijn verplicht (een ontbrekend onderdeel geeft This field is required.); addition is optioneel; lat en lon zijn optionele getallen (breedtegraad -90 tot 90, lengtegraad -180 tot 180). Spaties aan de randen worden verwijderd en een leeg onderdeel telt als ontbrekend. De server voegt country toe uit config.country. Een Nederlandse (NL) postcode bestaat uit vier cijfers en twee letters, met of zonder spatie of koppelteken, en wordt opgeslagen als 1234 AB; voor andere landen is een postcode 2 tot 12 tekens. Na het verwijderen van de spaties is postal_code hoogstens 12 tekens, house_number hoogstens 20, addition hoogstens 10, en street en city elk hoogstens 255. Al het andere dat aan de waarde mankeert (geen object, een ongeldige postcode, een te lang onderdeel, coördinaten buiten bereik) geeft Must be a valid address. Een regel kan een adres alleen testen met filled of empty.
Met config.lookup: true kan je formulier street, city, lat en lon invullen op basis van de postcode en het huisnummer met GET /v1/api/forms/address-lookup. Opzoeken is alleen beschikbaar voor NL; de merchant kan het niet aanzetten voor een ander land. De bezoeker kan straat en plaats ook zelf typen, bijvoorbeeld als het opzoeken niets vindt.
config.service_area is {origin: {lat, lon, label?}, radius_km, mode, message}. radius_km is meer dan 0 en hoogstens 1000, mode is notice of block en message is de tekst die bij een adres buiten het gebied wordt getoond. De server meet de afstand hemelsbreed in kilometers (één decimaal) van origin tot de coördinaten van het adres en bewaart {distance_km, in_area} per adresveld in meta.address van de inzending, die de merchant in de inbox ziet. Hoe de coördinaten worden bepaald en wanneer block een inzending weigert, staat onder Adresvelden en het werkgebied.
Wat telt als leeg
Een antwoord is leeg als het null is, een string met alleen spaties, tabs of regeleinden, een lege lijst [], een leeg object {} of false. Al het andere, ook 0, is een antwoord. Een leeg antwoord op een verplicht zichtbaar veld is een fout; een leeg antwoord op een optioneel veld wordt gewoon niet opgeslagen.
Regels
show_if op een stap of veld bepaalt of die wordt getoond. Het is één voorwaarde, of één groep voorwaarden:
{ "field": "services", "op": "contains", "value": "website" }
{ "all": [
{ "field": "services", "op": "contains", "value": "website" },
{ "field": "budget", "op": "not_equals", "value": "small" }
] }
{ "any": [
{ "field": "job_type", "op": "equals", "value": "roof" },
{ "field": "job_type", "op": "equals", "value": "paint" }
] }all klopt als elke voorwaarde klopt, any als er minstens één klopt. Groepen kunnen niet genest worden. Een voorwaarde noemt een invoerveld dat eerder in het formulier staat: voor de regel van een veld een vraag ervóór (in een eerdere stap of hoger in dezelfde stap); voor de regel van een stap een vraag in een eerdere stap.
op | Klopt als |
|---|---|
equals | Een lijstantwoord precies één item heeft en dat gelijk is aan value; elk ander antwoord gelijk is aan value. Een leeg antwoord is nooit gelijk. |
not_equals | equals niet klopt (een leeg antwoord klopt dus wel). |
contains | Een lijstantwoord een item bevat dat gelijk is aan value; elk ander antwoord gelijk is aan value. |
filled | Het antwoord niet leeg is. value wordt genegeerd. |
empty | Het antwoord leeg is. value wordt genegeerd. |
value is een string, getal of boolean bij equals, not_equals en contains. Waarden worden als tekst vergeleken: strings zoals ze zijn, true en false als "true" en "false", gehele getallen zonder decimalen (2, 2.0 en "2" zijn gelijk), andere getallen in hun kortste vorm (2.5). true is gelijk aan "true" maar niet aan 1; objecten en null zijn nooit ergens aan gelijk.
Loop het formulier op volgorde door. Een stap wordt getoond als zijn regel klopt; een veld als zijn stap wordt getoond en zijn eigen regel klopt. Een voorwaarde leest een antwoord alleen zolang dat veld wordt getoond: het antwoord van een verborgen veld leest als leeg, dus wie een veld verbergt, verbergt ook alles wat ervan afhangt. De server past bij het versturen precies deze regels toe: antwoorden op velden die op dat moment verborgen zijn, vallen zonder fout weg en zijn niet verplicht. Uploadtokens voor een verborgen bestandsveld vallen ook weg en worden niet geclaimd.
Verborgen velden
Een hidden-veld wordt nooit getoond, maar de waarde wordt met de inzending opgeslagen (bijvoorbeeld een campagnecode of de pagina waar de bezoeker vandaan kwam).
source: "query"(standaard): neem de waarde uit de queryparameter van de pagina-URL met dezelfde naam als de veld-key, bijvoorbeeld?campaign=springvoor een veld met keycampaign.source: "site": je site zet de waarde zelf wanneer het formulier wordt getoond.
De waarde is een string van maximaal 500 tekens. Zichtbaarheidsregels gelden ook voor verborgen velden.
Vooraf invullen vanuit de URL
Een veld met config.prefill op true mag worden ingevuld vanuit de queryparameter van de pagina-URL met dezelfde naam als de veld-key (?email=ada@example.com). De bezoeker kan het nog wijzigen; de server behandelt het als een gewoon antwoord. Toestemmings- en bestandsvelden worden nooit vooraf ingevuld.
Talen
Formulierteksten worden in het dashboard per taal geschreven. GET /v1/api/forms/{key}?locale= geeft elke tekst terug in die taal, met de standaardtaal van de winkel als terugval en daarna een willekeurige taal die een tekst heeft. Een locale die niet is ingeschakeld voor de winkel levert de standaardtaal (geen fout). Stuur dezelfde locale mee bij de inzending: die bepaalt de taal van het bedankbericht in de response en van de bevestigingsmail aan de bezoeker.
Bezoekers-IP en de limiet per uur
Elk formulier heeft een limiet op het aantal inzendingen per bezoeker per uur, ingesteld door de merchant (1 tot 100, standaard 5). Elke poging telt, ook pogingen die met 422 op validatie stranden; boven de limiet antwoordt de API 429 {"message": "Too many submissions. Try again later."}. Het uur begint bij de eerste poging van de bezoeker.
Borkol herkent de bezoeker aan de header X-Borkol-Visitor-Ip, die je server vult met het IP-adres van de bezoeker (IPv4 of IPv6). Vertrouw alleen een adres dat je eigen proxy of host zet: de eerste hop van X-Forwarded-For kan de bezoeker vervalsen, tenzij een vertrouwde proxy hem overschrijft. Ontbreekt de header of is het geen geldig IP-adres, dan gebruikt Borkol het adres van de verbinding, en dat is je server: al je bezoekers delen dan één limiet. De header wordt alleen op de formulierroutes gelezen.
Het ruwe IP-adres wordt nooit opgeslagen: de inzending bewaart een HMAC-SHA256-hash ervan.
Uploads hebben een eigen limiet: 30 per formulier per bezoeker per uur, op dezelfde manier geteld (zie Uploads).
Honeypot
Voeg een invoerveld toe dat mensen nooit zien (verborgen met CSS, niet type="hidden", met tabindex="-1" en autocomplete="off") en stuur de waarde mee als honeypot. Bots vullen vaak elk veld in. Heeft honeypot een andere waarde dan een lege string of null, dan antwoordt Borkol 201 met number: null en de bedankinstellingen van het formulier, slaat niets op, verstuurt geen mail of webhook, controleert Turnstile niet en telt de poging niet mee voor de limiet per uur.
Turnstile
Een merchant kan Cloudflare Turnstile per formulier aanzetten en slaat de Turnstile-site key en secret key van de winkel één keer op in het dashboard. Heeft het formulier Turnstile aan en zijn beide keys ingesteld, dan geeft GET /v1/api/forms/{key} settings.turnstile_site_key terug; anders ontbreekt het.
- Render de Turnstile-widget met die site key en stuur het token dat hij oplevert mee als
turnstile_tokenbij de inzending. - Borkol controleert het token bij Cloudflare (met het bezoekers-IP uit
X-Borkol-Visitor-Ip) na de limiet per uur en vóór de controle van de antwoorden. Een ontbrekend, ongeldig, verlopen of al gebruikt token, een fout bij Cloudflare of een controle die langer duurt dan 5 seconden geeft422met de fout opturnstile:The spam check failed. Reload the page and try again. - Een token werkt maar één keer. Omdat het token vóór de antwoorden wordt gecontroleerd, heeft elke
422(ook een fout antwoord) het opgebruikt. Reset de widget na elke422en stuur het nieuwe token mee bij de volgende poging. - Een mislukte controle telt wel mee voor de limiet per uur.
Heeft het formulier Turnstile aan, maar heeft de winkel niet beide keys ingesteld, dan wordt er niets gecontroleerd: turnstile_site_key ontbreekt, een turnstile_token wordt genegeerd en inzendingen worden zonder controle geaccepteerd. Het dashboard waarschuwt de merchant hiervoor bij het formulier en op de pagina met Forms-instellingen.
Wat er na een inzending gebeurt
Borkol slaat de zichtbare antwoorden op (inclusief waarden van hidden-velden), samen met de formulierversie, de taal en de paginagegevens die je meestuurt. De name van de bezoeker komt uit het veld met key name, email uit het eerste beantwoorde email-veld en phone uit het eerste beantwoorde phone-veld. Geüploade bestanden worden aan de inzending gekoppeld en privé opgeslagen: de merchant opent ze vanuit de inbox, en er bestaat geen publieke URL. Komt dat e-mailadres overeen met precies één contact van de winkel (hoofdletterongevoelig), dan wordt de inzending aan dat contact gekoppeld; Borkol maakt nooit een contact aan.
Nadat de inzending is opgeslagen, mailt Borkol de merchant, stuurt de bezoeker een bevestiging als het formulier die heeft en de bezoeker een e-mailadres gaf, voegt een melding toe in het dashboard en verstuurt de webhook form_submission.created. Geen van deze stappen kan het request laten mislukken: eenmaal opgeslagen is het antwoord 201.
De publieke API heeft geen endpoint om inzendingen terug te lezen. De merchant handelt ze af in de inbox van het dashboard.
Het pakket @borkol/forms
@borkol/forms is een TypeScript-pakket zonder dependencies (MIT) met de types van deze endpoints en dezelfde regels als de server, zodat je site dezelfde stappen en dezelfde fouten kan tonen voordat hij verstuurt. Installeer @borkol/forms; React is een optionele peer, alleen gebruikt door @borkol/forms/react.
import { visible, check, prefill, type PublicForm } from "@borkol/forms";
const form: PublicForm = (await res.json()).data;
visible(form, answers); // { steps: ["what", ...], fields: ["services", ...] }
check(form, answers); // { errors: { "answers.email": ["Must be an email address."] }, clean: {...} }
check(form, answers, "what"); // check one step only
prefill(form, new URLSearchParams(location.search));visiblegeeft de keys van de getoonde stappen en velden terug.checkgeefterrorsterug met keysanswers.{key}en de Engelse meldingen van de server (geëxporteerd alsMESSAGES, zodat je ze naar je eigen vertalingen kunt omzetten) enclean, de antwoorden om te versturen: velden die door hun regels verborgen zijn, weergavetypes en onbekende keys zijn eruit. Waarden van het typehiddenblijven staan.prefillleest de antwoorden voor velden metconfig.prefillenhidden-velden met bronqueryuit de queryparameters van de URL.
@borkol/forms/react rendert de stappen voor je met je eigen componenten:
import { BorkolForm } from "@borkol/forms/react";
<BorkolForm
form={form}
locale="nl"
onSubmit={(body) => submitToMyServer(body)} // resolves to { number, thank_you }
components={{
text: ({ field, value, onChange, errors }) => (
<label>{field.label}<input value={(value as string) ?? ""} onChange={(e) => onChange(e.target.value)} />{errors[0]}</label>
),
heading: ({ field }) => <h2>{field.label}</h2>,
nav: ({ isFirst, isLast, back, submitting }) => (
<>{!isFirst && <button type="button" onClick={back}>Back</button>}<button disabled={submitting}>{isLast ? "Send" : "Next"}</button></>
),
done: ({ result }) => <p>{result.thank_you.message}</p>,
}}
/>;onSubmit krijgt de body van de inzending, {locale, answers, files?, turnstile_token?} met de gecontroleerde antwoorden; post die naar je eigen server, die er meta en honeypot aan kan toevoegen en hem doorstuurt met de forms:write-sleutel en de header met het bezoekers-IP. Om de 422-fouten van de server te tonen, gooi je de response-body (het object met errors) vanuit onSubmit: fouten op getoonde vragen verschijnen daar, elke andere key (turnstile, een vraag die nu verborgen is) gaat naar formErrors, dat het nav-component en het formError-component krijgen. Al het andere dat je gooit (een netwerkfout) toont daar een algemene melding; submit() wordt nooit afgewezen.
Meer opties:
upload(field, file)slaat één bestand op voor eenfile-veld: post het als multipart (field,file) naar een route op je server die het doorstuurt naarPOST /v1/api/forms/{key}/uploads, en resolve naar dedatavan de response ({token, name, size}). Gooi de422-body om de melding onder de vraag te tonen. Het antwoord van een bestandsveld is dan de lijst geüploade bestanden; hetfile-component krijgtupload,remove,uploadingenaccept(voor het attribuutacceptvan het invoerveld). Het pakket controleertmax_filesenmax_mbvóór het uploaden en verstuurt de tokens onderfiles.turnstileTokenis het huidige token van de Turnstile-widget, verstuurd alsturnstile_token.onServerErrors(errors)wordt aangeroepen na elke mislukte verzending. Reset daar de Turnstile-widget: het token is opgebruikt.persistKeybewaart het concept van de bezoeker (antwoorden en huidige stap) inlocalStorage[persistKey]en herstelt het direct na het mounten, zodat een herlaad verdergaat waar de bezoeker was.initialAnswersgaan voor bij de keys die ze zetten, bestandsantwoorden worden niet bewaard en het concept wordt na een geslaagde inzending verwijderd. Een concept ouder dan 30 dagen wordt genegeerd en verwijderd. ZonderlocalStoragewerkt het formulier zoals voorheen.- Een
address-veld krijgt de gewonecomponents.address-props (field,value,onChange,errors); het opzoeken blijft in je eigen component.formatAddress,normaliseAddressendistanceKmworden vanuit de root van het pakket geëxporteerd.
useBorkolForm({ form, initialAnswers, persistKey, locale, onSubmit, upload, turnstileToken, onServerErrors }) geeft dezelfde state voor een volledig eigen layout: step, stepIndex, steps, fields, answers, errors (per veld-key), formErrors, setAnswer, upload(key, files), removeFile(key, token), uploading, next() (controleert de huidige stap), back(), submit(), submitting en result (result.number is null na een honeypot-treffer). Geef hidden-velden met bron site mee in initialAnswers.
De server blijft leidend: hij controleert elke inzending opnieuw, wat de site ook deed.