Sawala CloudSawala Cloud — Docs
Formulir

How to embed a form

Three ways to embed a Formulir form on your site — hosted iframe, the React component, or a script tag. When to pick which, with the correct embed URLs.

TODO: translate to Indonesian. English source is authoritative; render it into Indonesian below.

Formulir supports three embed modes. Pick by integration need.

Comparison

IframeReact componentScript tag
SetupPaste one HTML tagnpm install @sawala/formulir-reactTwo tags + a mount <div>
RenderingCross-origin iframeInline in your DOMInline in your DOM
StyleIsolated from your siteInherits + themeableThemeable via CSS variables
FitsAny site (WordPress, Webflow, static HTML)React 18+ apps (Next.js, Vite)Non-React hosts that want native DOM
Custom renderingNoYes — headless mode and hooksNo

All three modes require a project-scoped publishable key (pk_live_…) for the formulir product. See API keys and access.

Iframe embed

The simplest option. The form is rendered and hosted by Sawala at https://formulir.id.

<iframe
  src="https://formulir.id/embed/<projectId>/<formSlug>?key=pk_live_…"
  style="width: 100%; height: 600px; border: 0;"
  loading="lazy"
  title="Contact us"
></iframe>

The ?key=pk_live_… parameter is required — a keyless embed URL renders an error page. The key must be project-scoped to the project given in <projectId>. The dashboard form editor generates this URL with your project ID and key already filled in.

The iframe is a fixed-size HTML page, so give it a height that fits your form. It automatically adapts to light and dark mode based on the visitor's system preference.

Pre-filling and hidden fields

To pre-populate fields, add values[fieldName]=... query parameters. This is the only way to supply a value for a field marked Hidden in the form editor:

<iframe
  src="https://formulir.id/embed/<projectId>/<formSlug>?key=pk_live_…&values[report_slug]=annual-report-2024"
></iframe>

Locale

If the form's operator filled in per-locale labels and success messages, pick one with ?locale=:

<iframe src="https://formulir.id/embed/<projectId>/<formSlug>?key=pk_live_…&locale=id"></iframe>

React component

For React 18+ apps. Install the package, import the stylesheet, wrap your tree in a provider, and render the form.

npm install @sawala/formulir-react
'use client'
import '@sawala/formulir-react/styles.css'
import { FormulirProvider, FormulirForm } from '@sawala/formulir-react'

export default function ContactPage() {
  return (
    <FormulirProvider apiKey="pk_live_…">
      <FormulirForm slug="contact" onSubmit={(s) => console.log(s)} />
    </FormulirProvider>
  )
}

<FormulirProvider> props

PropTypeDescription
apiKeystringProject-scoped publishable key (required)
baseUrlstringOverride the gateway URL (default https://api.sawala.cloud/public/formulir)
appearanceFormulirAppearanceClerk-style theming — variables (CSS variables) and elements (class names per slot)
localestringDefault BCP-47 locale for every form in this tree

<FormulirForm> props

PropTypeDescription
slugstringForm slug (required)
onSubmit(result) => voidCalled after a successful submission
onError(error) => voidCalled when submission fails
appearanceFormulirAppearancePer-form theming overrides (merged over the provider's)
classNamestringCSS class on the form root
submitLabelstringOverride the submit button label
submittingLabelstringOverride the label shown while submitting
valuesRecord<string, unknown>One-shot pre-fill applied when the definition resolves (use this for hidden fields)
localestringPer-form BCP-47 locale (overrides the provider's locale)

Pre-fill uses the values prop. It is a one-shot pre-fill applied once when the form definition resolves, not a controlled-input mechanism — subsequent changes to the prop are ignored.

Theming

Three escalating tiers:

  1. CSS variables — override any --formulir-* variable in your own CSS.
  2. appearance prop — pass variables (inline CSS variables) and elements (class names merged into slots such as submitButton, inputField, errorText).
  3. Headless — take full control of the markup with <FormulirForm.Headless> or the useFormulirForm hook.
import { useFormulirForm } from '@sawala/formulir-react'

const { definition, values, errors, status, submitting, error,
        setValue, submit, reset } = useFormulirForm({ slug: 'contact' })
<FormulirForm.Headless slug="contact">
  {({ definition, values, errors, setValue, submit, submitting, status }) => (
    /* render whatever markup you like */
  )}
</FormulirForm.Headless>

Script tag

For non-React hosts that still want the form rendered natively in the DOM (rather than in an iframe). Drop in the stylesheet, the script with your key, and a mount <div>:

<link rel="stylesheet" href="https://formulir.id/formulir.css">
<script src="https://formulir.id/formulir.js" data-formulir-key="pk_live_…"></script>
<div data-formulir-slug="contact"></div>

Spam protection (Turnstile)

Formulir uses Cloudflare Turnstile.

  • Iframe — zero-config. When you enable spam protection on a form, the hosted iframe renders a Sawala-managed Turnstile challenge automatically.
  • React component and script tag — bring-your-own. In Settings → Projects → Edit → Spam protection, paste your own Turnstile sitekey and secret (from dash.cloudflare.com → Turnstile), then flip the per-form Spam protection switch. The React component then renders the challenge automatically — no extra code.

In headless mode you render Turnstile yourself and pass the token to submit({ 'cf-turnstile-response': token }).

Spam-protection errors surface as CAPTCHA_REQUIRED, CAPTCHA_FAILED, or CAPTCHA_NOT_CONFIGURED. See the API reference.

Content Security Policy

If your site sends a strict Content-Security-Policy, allow:

frame-src   https://formulir.id
connect-src https://api.sawala.cloud

When Turnstile is enabled on the React or script-tag paths, also allow Cloudflare's challenge host:

script-src  https://challenges.cloudflare.com
frame-src   https://challenges.cloudflare.com

Daftar Isi