Skip to main content
Formo
The Formo Web SDK is open source and implements the standard Events API.

Installation

Use this pre-built prompt to get started faster: Install Formo with AI.
There are several ways to install the Formo SDK: We recommend installing Formo on both your website (example.com) and your app (app.example.com) on the same project with the same SDK write key. The standard setup is to use the HTML snippet on your website and Wagmi on your app, with cross-subdomain tracking enabled.

Wagmi

If you’re already using Wagmi, this is the recommended way to install Formo for apps.When wagmi options are provided, the SDK hooks directly into Wagmi’s state management instead of wrapping EIP-1193 providers. This provides:
  • Native event handling via Wagmi’s built-in state system
  • Better compatibility with wallet connection libraries (RainbowKit, ConnectKit, etc.)
  • Full tracking of signatures and transactions via TanStack Query’s mutation cache
Note that transaction and signature autocapture requires the use of wagmi React hooks
Install the Web SDK via NPM.

HTML Snippet

Install this snippet at the <head> of your website:
Enable Subresource Integrity (SRI) to improve site security.

React & Next.js (without Wagmi)

Use Wagmi for more reliable and secure event tracking.
Install the Web SDK via NPM.

Angular

FormoAnalyticsProvider and useFormo() are React-only. Angular apps import from the React-free @formo/analytics/core subpath and wrap FormoAnalytics.init() in an injectable service, with wallets connected over the bare EIP-1193 provider (window.ethereum). Full working example: with-angular.
Install the Web SDK along with the buffer polyfill. Angular’s esbuild build doesn’t auto-polyfill Node globals, but the SDK uses Buffer to decode signed-message payloads. Without it, signing throws ReferenceError: Buffer is not defined.
Wrap FormoAnalytics.init() in an injectable service. Import from @formo/analytics/core; the root entry pulls in the React provider, which Angular doesn’t need:
Initialize it with provideAppInitializer so the SDK’s autocapture wraps window.ethereum before any wallet interaction:

Identify users

Call identify() after a user connects their wallet or signs in on your website or app:
If no parameters are specified, the Formo SDK will attempt to auto-identify the wallet address.
Call identify() in a useEffect that triggers when the wallet address changes:

Track events

The Web SDK automatically captures common events such as page views and wallet events (connect, disconnect, signature, transaction, etc) with full attribution (referrer, UTM, referrals.) You do not need to manually track them.
To track custom events (in-app user actions, key conversions) use the track function with details of what happened:
Using a standardized naming convention for your custom events is recommended. You can track volume, revenue, and points in custom events.

Code examples

Privy

examples/with-privy

Dynamic

examples/with-dynamic

Turnkey

examples/with-turnkey

React

examples/with-react

Next.js (app router)

examples/with-next-app-router

Next.js (pages router)

examples/with-next-page-router

MetaMask SDK

examples/with-metamask

Thirdweb

examples/with-thirdweb

Reown

examples/with-reown

Crossmint

examples/with-crossmint

Openfort

examples/with-openfort

Angular

examples/with-angular

Solana

examples/with-solana

React Native

examples/with-react-native

Configuration

Local testing

The SDK skips tracking in localhost by default. To enable tracking locally during development, set tracking to true:

Logging

Control the level of logs the SDK prints to the console with the following logLevel settings:
Logging is disabled by default (logger.enabled: false); no logs print unless you explicitly set logger.enabled: true. The levels array is matched exactly, not hierarchically: setting levels: ["warn"] shows only warn messages, not warn and error together. List every level you want to see.

Autocapture

You can configure which wallet events are automatically captured by the SDK:

Batching

To support high-performance environments, the SDK sends events in batches.
Customize this behavior with the flushAt and flushInterval configuration parameters.

Ready callback

The ready callback function executes once the Formo SDK is fully loaded and ready to use. This is useful for performing initialization tasks or calling SDK methods that require the SDK to be ready.
For Browser installations, you can use the ready callback in the onload attribute:

Environments

You can control tracking behavior in different environments (test, staging) with the tracking option:
Specify exact hostnames and exact paths in exclusion lists.

Excluding by timezone

When the visitor’s browser-resolved timezone (via Intl.DateTimeFormat().resolvedOptions().timeZone) matches an entry in excludeTimezones, the SDK suppresses tracking entirely for that visitor. No events are enqueued or sent, including identify and connect, and no identity cookie is written. This is useful for opting whole regions out of analytics (for example, to honor a jurisdiction’s privacy expectations).

Excluding query parameters

Formo automatically captures the page URL, query string, individual query-parameter properties, and the referrer. If your URLs carry sensitive data (auth tokens, one-time codes, emails), use tracking.excludeQueryParams to strip those parameters in the browser, before any event is sent so they are never transmitted or stored:
These parameters are applied on top of an always-on built-in denylist that cannot be disabled. The following parameters are always stripped, regardless of configuration:
  • privy_oauth_code: Privy OAuth authorization code
  • privy_oauth_state: Privy OAuth CSRF state token
  • privy_oauth_provider: Privy OAuth provider identifier
Excluded parameters are removed from the url, query, referrer, and other event properties. Matching is case-insensitive. The Formo Web SDK includes simplified consent management functionality to help you comply with privacy regulations like GDPR, CCPA, and ePrivacy Directive. Control user tracking preferences with simple opt-out and opt-in methods:
For browser installations:

Cross subdomain tracking

By default, Formo sets identity cookies on the root domain, sharing visitor identity across all subdomains. This ensures accurate visitor counts and consistent attribution across your subdomains out of the box. With the default crossSubdomainCookies setting:
  • Cross subdomain tracking: track users as they move between your marketing site, app, docs, and other subdomains.
  • Accurate attribution: attribute conversions to the correct channel, even when users cross subdomains.
  • Automatic migration: the SDK migrates existing host-scoped cookies to the apex domain so visitors are not double-counted.
To disable cross-subdomain tracking and scope cookies to the current hostname only, set crossSubdomainCookies to false:

Referrals

Formo autodetects ref, referral, refcode, af, and referrer query parameters in the URL. You can customize how referrals are detected by the SDK:
In the above configuration, Formo will detect referrals from:
  • the via and ref query parameters in the URL, and
  • from the /referral/([^/]+) path pattern

Wagmi integration

For apps using Wagmi, enable native integration by providing the Wagmi config and QueryClient:
Transaction and signature autocapture requires the use of wagmi React hooks (useWriteContract, useSendTransaction, useSignMessage). Calls via @wagmi/core or viem directly bypass the mutation cache and won’t be tracked. If you are not using these hooks, use the non-wagmi React or Next.js installation methods instead.
When Wagmi mode is enabled, the SDK:
  • Hooks directly into Wagmi’s state management for connection events
  • Uses TanStack Query’s mutation cache for signature and transaction tracking
  • Skips EIP-1193 provider wrapping (no proxy behavior)
Use the same QueryClient instance for both Wagmi and Formo to avoid creating multiple cache instances.

Solana integration

The Formo Web SDK supports Solana via framework-kit (@solana/client + @solana/react-hooks). The SDK subscribes to framework-kit’s zustand store as a read-only observer. It never wraps or intercepts wallet methods. See the full Solana example app for a working implementation.
Autocaptured events The following Solana wallet events are autocaptured via zustand store subscription: Manually tracking Solana transactions and signatures Transaction and signature events for Solana wallets must be tracked explicitly because framework-kit’s React hooks (useSolTransfer, useSendTransaction, useTransactionPool) manage transaction state locally and don’t write to the store.
For Solana-only apps, set evm: false to disable EVM provider detection (EIP-1193 / EIP-6963). This prevents unnecessary tracking of injected EVM wallets.

FAQ

The Wagmi integration automatically tracks wallet connects, disconnects, chain switches, transactions, and signatures by hooking into Wagmi’s wallet adapter. The standard React integration tracks wallet events by wrapping the EIP-1193 wallet provider to track signatures and transactions. Use the Wagmi integration if your dApp uses Wagmi and its hooks.
Use formo.track() to send custom events with any properties you need. For example: formo.track('Swap Completed', { pair: 'ETH/USDC', token_in: 'ETH', token_out: 'USDC', amount_in: 1.5 }). Custom events appear in the Activity feed and can be queried in the Explorer.
Yes. The SDK includes a dedicated Privy integration with parsePrivyProperties() to extract profile properties and linked wallet addresses. For other wallet providers, use the standard React integration. If your wallet provider uses Wagmi under the hood (many do, including Privy), you can also use the Wagmi integration for automatic wallet event tracking.

Proxy

To handle ad-blockers and privacy browsers, we recommend setting up a reverse proxy.
Reverse proxy data flow

What happens under the hood when you forward the data to Formo via a reverse proxy

Here are the domains used by Formo to load the SDK and send data:

CDN

Some ad blockers block specific scripts and API calls based on the domain name and URL. To address this issue you can download and serve the Formo HTML Snippet from cdn.formo.so to your own domain e.g. yourdomain.com/formo.js.
If you use the Web SDK, you can skip this because they are not loaded from the CDN.

Next.js rewrites

If you are using Next.js, you can take advantage of rewrites to behave like a reverse proxy. To do so, add a rewrites() function to your next.config.js file:
Then, configure the Formo SDK to send requests via your rewrite. Update the apiHost parameter in the Formo SDK:
See an example Next.js app here.
If you are using the HTML / website snippet, replace the install script to:

Next.js middleware

If you are using Next.js and rewrites aren’t working for you, you can write custom middleware to proxy requests to Formo. Create a file named middleware.js/ts in your base directory (same level as the app folder).
In this file, set up code to match requests to a custom route, set a new host header, change the URL to point to Formo, and rewrite the response. Once done, configure the Formo SDK to send requests via your rewrite:

Others

Alternatively, you can set up your own proxy (with Cloudfront, Cloudflare, etc) and pass the URL as the SDK apiHost:

Verification

To verify the proxy is working:
  • Visit your website / app
  • Open the network tab in your browser’s developer tools
  • Check that analytics requests are going through your domain instead of events.formo.so
  • Check that events show up in the Activity page on the Formo dashboard