Getting Started

Browser

The Browser docs cover the front-end JavaScript and CSS used by Formie’s rendered forms.

If you are coming from the Formie plugin docs, this is where to look when you want to understand or customize what happens in the browser: JavaScript events, submit handling, validation, field modules, rendered markup, CSS variables, and the default theme CSS.

There are two common paths:

  • Plugin-rendered forms - Craft renders the form HTML and Formie’s normal front-end assets enhance it. Use these docs to find events, CSS hooks, module behavior, and markup contracts.
  • Your own frontend bundle - You import @verbb/formie-browser, decide when forms mount, and choose whether to load Formie’s CSS or provide your own. Use this path when you disable Formie’s automatic JavaScript, load forms from an endpoint, or need lower-level client control.

If React, Vue, or Web Components owns the form surface, use those package docs instead. They build on the same front-end concepts, but the integration point is the framework component rather than a Craft-rendered form already on the page.

Installation

You only need to install @verbb/formie-browser when your own build imports the package. Plugin-rendered forms can use the assets emitted by Formie without installing the npm package in your project.

bash
npm install @verbb/formie-browser

Import the shipped CSS if you want the default Formie UI:

ts
import '@verbb/formie-browser/css/formie.css';

If Formie already renders the form

For most Craft-rendered forms, Formie outputs the markup, startup script, translations, modules, and CSS links needed for the front end. You do not need to recreate that setup from npm just to listen for events or adjust styling.

Start here when you want to customize what the plugin already provides:

  • JavaScript events for lifecycle, page navigation, validation, and submit events.
  • Submission handling for Ajax results and submit-flow behavior.
  • UI reference for rendered markup, data attributes, and class hooks.
  • CSS variables for changing the default theme without replacing templates.
  • Modules for field, captcha, address, and payment behavior.

If you are moving old event listeners or custom scripts from older plugin-rendered forms, see Migrating from Formie Plugin.

If your bundle initializes forms

Use @verbb/formie-browser when Formie still owns the rendered HTML and browser behavior, but your frontend bundle owns mounting.

Start with formie({ element }). It wraps the lower-level browser client with DOM-ready handling, mounts the matched form elements, and gives you simple success and error hooks.

ts
import { formie } from '@verbb/formie-browser';
import '@verbb/formie-browser/css/formie.css';

await formie({
  element: '#contact-form',
  onReady(instance) {
    console.log('Mounted:', instance.id);
  },
  onSuccess(result) {
    console.log('Submit ok:', result);
  },
  onError(result) {
    console.log('Submit failed:', result);
  },
});

element can be:

  • a CSS selector such as '#contact-form'
  • one DOM element
  • any iterable collection of elements

When you pass a selector, Formie waits until the DOM is ready before it gives up on finding the target.

If Craft still renders the form HTML but you want to control startup timing, use Manual initialization. If you need custom module registration, explicit mount/unmount control, or endpoint-driven loading, use Custom client.

Options

formie() supports these options:

NameTypeRequiredDescription
elementstring | Element | Iterable<Element>YesDOM target or selector to mount.
transport'rest' | 'graphql'NoUse when Formie should fetch HTML payloads itself instead of only enhancing existing rendered markup.
endpointstringNoCraft site base URL for REST, or GraphQL endpoint for GraphQL.
formHandlestringNoForm handle to load when you are not mounting an already rendered form.
payloadFormEndpointPayloadNoPreloaded HTML payload for explicit mounting.
refreshTokensbooleanNoRefresh CSRF, request, render, and captcha tokens as needed.
localestringNoAdvanced locale override.
siteIdnumberNoRequest a specific site.
autoVisiblebooleanNoMount eagerly instead of waiting for visibility-driven initialization.
theme'formie' | 'none'NoUse the shipped Formie theme or skip it.
themeConfigRecord<string, unknown>NoAdditional browser-theme configuration.
observebooleanNoStart low-level DOM observation after the initial mount.
onReadyfunctionNoCalled after each matched form instance mounts.
onResultfunctionNoCalled for every submit result.
onSuccessfunctionNoCalled when a submit result succeeds.
onErrorfunctionNoCalled when a submit result fails.
onEventfunctionNoCalled for browser lifecycle events exposed by the mounted instance.

Return value

formie() resolves to a small app handle:

NameTypeDescription
clientFormieClientThe underlying advanced browser client.
instancesFormieFormInstance[]Mounted instances currently tracked by the helper.
get(target)functionReturns one mounted instance by selector or element.
rescan()functionRe-runs the element lookup and mounts any new matches.
destroy()functionUnsubscribes listeners, stops observation, and unmounts tracked forms.

Existing rendered forms

If Craft already rendered the final form HTML into the page and you are initializing from your own bundle, formie() is usually all you need:

html
<form id="contact-form" data-formie-form>
  ...
</form>
ts
await formie({
  element: '#contact-form',
});

On plugin-rendered pages, Craft can also output Formie's startup script and an inline JSON translations seed for you. formie() is for the cases where you want to take over mounting timing from your own bundle, not for re-creating the whole plugin startup layer.

When you need the advanced client

Use the lower-level browser client only when you need a real story for it, such as:

  • custom module registration
  • explicit mount() / unmount() / update() control
  • manual scan() / observe() coordination in a larger app shell
  • migration code that already depends on the full browser client surface

That path lives on Custom client.

If Formie is still outputting its browser script for you and you only need to take over boot timing, use Manual initialization instead.

Last updated: May 6, 2026, 3:46 PM