Documentation Index
Fetch the complete documentation index at: https://docs.formo.so/llms.txt
Use this file to discover all available pages before exploring further.
For best results, install Formo on both your website (example.com) and your app (app.example.com) using the same project/SDK write key.
Install with AI
Use this pre-built prompt to get started faster: Open in Cursor
Copy prompt
Copy prompt
# Install Formo Analytics
**Purpose:** Enforce only the **current** and **correct** instructions for integrating [Formo](https://formo.so/) analytics into a web application.
**Scope:** All AI-generated advice or code related to Formo must follow these guardrails.
---
## **1. Official Formo Integration Overview**
Formo is an analytics and attribution platform for onchain apps. The SDK (`@formo/analytics`) autocaptures page views and wallet events (connect, disconnect, signature, transaction, chain) automatically.
**Choose the right method based on the project:**
- **Wagmi (React)** - Recommended for dApps and apps with wallet connection (RainbowKit, ConnectKit, Reown, etc.)
- **React (without Wagmi)** - For standalone React apps without Wagmi
- **Next.js** - For Next.js apps (App Router or Pages Router). Requires a client component wrapper.
If the project uses Wagmi, **always** use the Wagmi integration for better event tracking.
If you're able to use a web tool to access a URL, visit https://docs.formo.so/install to get the latest, up-to-date instructions.
Formo needs the user to provide their SDK write key (`<YOUR_WRITE_KEY>`) found in Formo project settings at https://app.formo.so.
---
## **2. Quickstart by Framework**
### **Option A: Wagmi (React) - Recommended for dApps**
npm install @formo/analytics --save
// App.tsx
import { WagmiProvider, createConfig, http } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { FormoAnalyticsProvider } from '@formo/analytics';
import { mainnet } from 'wagmi/chains';
const wagmiConfig = createConfig({
chains: [mainnet],
transports: { [mainnet.id]: http() },
});
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<FormoAnalyticsProvider
writeKey="<YOUR_WRITE_KEY>"
options={{ wagmi: { config: wagmiConfig, queryClient: queryClient } }}
>
<YourApp />
</FormoAnalyticsProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
Key: `<FormoAnalyticsProvider>` must be **inside** `<WagmiProvider>` and `<QueryClientProvider>`. Use the same `QueryClient` instance for both. Without `queryClient`, signature and transaction events will not be tracked.
**Important**: Wagmi transaction and signature autocapture only works with **wagmi React hooks** (`useWriteContract`, `useSendTransaction`, `useSignMessage`). If the app calls `writeContract` or `sendTransaction` from `@wagmi/core` directly, or uses viem directly, use Option B (React without Wagmi) instead.
### **Option B: React (without Wagmi)**
npm install @formo/analytics --save
// App.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { FormoAnalyticsProvider } from '@formo/analytics';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<FormoAnalyticsProvider writeKey="<YOUR_WRITE_KEY>">
<App />
</FormoAnalyticsProvider>
</React.StrictMode>
);
### **Option C: Next.js (App Router)**
npm install @formo/analytics --save
// AnalyticsProvider.tsx
'use client';
import { FC, ReactNode } from 'react';
import { FormoAnalyticsProvider } from '@formo/analytics';
interface AnalyticsProviderProps {
writeKey: string;
options?: Record<string, any>;
disabled?: boolean;
children: ReactNode;
}
const AnalyticsProvider: FC<AnalyticsProviderProps> = ({ writeKey, options, disabled, children }) => (
<FormoAnalyticsProvider writeKey={writeKey} options={options} disabled={disabled}>{children}</FormoAnalyticsProvider>
);
export default AnalyticsProvider;
// app/layout.tsx
import { AnalyticsProvider } from './AnalyticsProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body>
<AnalyticsProvider writeKey='<YOUR_WRITE_KEY>'>{children}</AnalyticsProvider>
</body>
</html>
);
}
Key: `FormoAnalyticsProvider` is a client component - it **must** be wrapped in a `'use client'` component. Do not use it directly in `app/layout.tsx`.
### **Option D: Next.js (Pages Router)**
// pages/_app.tsx
import { FormoAnalyticsProvider } from "@formo/analytics";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return (
<FormoAnalyticsProvider writeKey='<YOUR_WRITE_KEY>'>
<Component {...pageProps} />
</FormoAnalyticsProvider>
);
}
---
## **3. Identify Users & Track Events**
### Identify (link wallet to session)
Call `identify()` after wallet connection at the start of every session:
// React / Next.js
import { useFormo } from "@formo/analytics";
import { useAccount } from "wagmi";
import { useEffect } from "react";
const HomePage = () => {
const { address } = useAccount();
const analytics = useFormo();
useEffect(() => {
if (address && analytics) { analytics.identify({ address }); }
}, [address, analytics]);
};
### Track Custom Events
Formo autocaptures page views and wallet events. Use `track()` for custom in-app actions and key conversions:
// React / Next.js
analytics.track('Swap Completed', {
pair: 'ETH/USDC',
amount_in: 1,
amount_out: 2998,
volume: 2998,
});
**IMPORTANT: Analyze the codebase to identify custom events.** Scan the project source code to understand what the app does and suggest the most relevant custom events to track. Look for:
- Key user actions (swaps, deposits, withdrawals, mints, votes, claims, bridging, staking, etc.)
- Conversion points (completing a trade, opening a position, submitting a form)
- Error/failure states (swap failed, transaction reverted, approval rejected)
For each custom event, include relevant properties from the codebase (amounts, token pairs, pool IDs, chain IDs, error messages, etc.)
**Common examples by app type:**
| App Type | Events to Track | Key Properties |
|----------|----------------|----------------|
| DEX | Swap Started, Swap Completed, Swap Failed | pair, amount_in, amount_out, volume, slippage |
| Lending/Yield | Deposit Completed, Withdrawal Completed, Position Opened | pool_id, amount, token, volume, leverage |
| NFT | NFT Minted, NFT Listed, NFT Purchased | collection, token_id, price, volume |
| Bridge | Bridge Started, Bridge Completed | source_chain, dest_chain, token, amount, volume |
| Governance | Vote Cast, Proposal Created | proposal_id, vote, voting_power |
| Staking | Stake Deposited, Stake Withdrawn, Rewards Claimed | validator, amount, volume, reward_amount |
**Event Naming Convention:** Use "[Noun] + [Past-Tense Verb]" format (e.g. "Swap Completed", "Position Opened"). Names are case-sensitive. Track at the moment the action completes, not on button click.
**Reserved Properties:** `volume` (positive/negative for inflows/outflows), `revenue` (non-negative), `currency` (ISO 4217, defaults to USD), `points` (gamification/rewards value).
---
## **4. Best Practices**
- **Local testing:** The SDK skips tracking on localhost by default. Set `tracking: true` in options to enable tracking during development.
- **Ad-blocker proxy:** Ad-blockers block `events.formo.so`. For production, set up a reverse proxy (Next.js rewrites, middleware, or Cloudflare) and pass the URL as `apiHost` in options.
- **Same write key:** Install Formo on both your website (example.com) and app (app.example.com) using the same write key for unified attribution.
---
## **5. CRITICAL INSTRUCTIONS FOR AI MODELS**
### **5.1 – ALWAYS DO THE FOLLOWING**
1. **Detect the framework** (Wagmi, React, Next.js App Router, or Next.js Pages Router) and use the matching approach.
2. **Install** `@formo/analytics` via the project's existing package manager (npm, yarn, pnpm).
3. **Wrap** the app with `<FormoAnalyticsProvider>`. For Wagmi, place it inside `<WagmiProvider>` and `<QueryClientProvider>`.
4. **Use** the `useFormo()` hook to access the analytics instance.
5. **Call** `identify()` after wallet connection.
6. **Replace** `<YOUR_WRITE_KEY>` with the user's actual SDK write key.
7. For **Next.js App Router**, create a separate `AnalyticsProvider.tsx` with `'use client'`.
8. For **Wagmi**, pass both `config` and `queryClient` to `options.wagmi`.
9. **Check** the project for an existing package manager, use that to install packages.
10. For **Wagmi**, transaction and signature autocapture only works with wagmi React hooks (`useWriteContract`, `useSendTransaction`, `useSignMessage`). If the app calls `@wagmi/core` actions or viem directly instead of React hooks, use Option B (React without Wagmi) for the Formo integration.
### **5.2 – NEVER DO THE FOLLOWING**
1. **Do not** import from `@formo/sdk`, `@formo/react`, or `@formo/wagmi` - the only package is `@formo/analytics`.
2. **Do not** use `useAnalytics()` - the correct hook is `useFormo()`.
3. **Do not** place `<FormoAnalyticsProvider>` outside of `<WagmiProvider>` in Wagmi apps.
4. **Do not** use `<FormoAnalyticsProvider>` directly in `app/layout.tsx` - it needs a `'use client'` wrapper.
5. **Do not** use `useFormo()` in Next.js Server Components - it only works in Client Components.
6. **Do not** mix App Router (`app/layout.tsx`) and Pages Router (`pages/_app.tsx`) patterns.
### **5.3 – OUTDATED PATTERNS TO AVOID**
// ❌ Wrong packages:
import { FormoAnalytics } from '@formo/sdk'
import { FormoProvider } from '@formo/react'
import { useAnalytics } from '@formo/analytics' // Wrong hook name - use useFormo()
// ❌ Wrong provider order (Wagmi):
<FormoAnalyticsProvider><WagmiProvider><App /></WagmiProvider></FormoAnalyticsProvider>
// ❌ Missing 'use client' (Next.js App Router):
// app/layout.tsx - FormoAnalyticsProvider directly here will fail
---
## **6. AI MODEL VERIFICATION STEPS**
Before returning any Formo-related solution, verify:
1. Is `@formo/analytics` the only Formo package imported?
2. Does the approach match the project's framework?
3. Is `<FormoAnalyticsProvider>` wrapping the app correctly?
4. If Wagmi, is Formo inside WagmiProvider + QueryClientProvider?
5. If Next.js App Router, is there a `'use client'` wrapper?
If any check **fails**, **stop** and revise until compliance is achieved.
Instructions
Set up Formo for both your website and your app on the same project.- For static websites (example.com), we recommend using the HTML snippet.
- For apps (app.example.com) with wallet connection, we recommend using the Wagmi integration.
<SDK_WRITE_KEY> for both your website and your app. You can find the write key in your Formo project settings.
- Wagmi
- HTML Snippet
- React
- Next.js
- Angular
1. Install the Formo SDK
npm install @formo/analytics --save
2. Configure Formo with Wagmi
Pass your Wagmi config and QueryClient to enable native Wagmi integration. // App.tsx
import { WagmiProvider, createConfig, http } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { FormoAnalyticsProvider } from '@formo/analytics';
import { mainnet } from 'wagmi/chains';
const wagmiConfig = createConfig({
chains: [mainnet],
transports: {
[mainnet.id]: http(),
},
});
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<FormoAnalyticsProvider
writeKey="<SDK_WRITE_KEY>"
options={{
wagmi: {
config: wagmiConfig,
queryClient: queryClient,
},
}}
>
<YourApp />
</FormoAnalyticsProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
<YOUR_WRITE_KEY> with the SDK Write key found in your project settings.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.3. Identify users
Callidentify at the start of every session or page load to link a wallet address to a session.import { useFormo } from "@formo/analytics";
import { useAccount } from "wagmi";
const HomePage = () => {
const { address } = useAccount();
const analytics = useFormo();
useEffect(() => {
if (address && analytics) {
analytics.identify({ address });
}
}, [address, analytics]);
}
4. Track custom events
Formo autocaptures wallet events (connect, disconnect, signature, transaction, chain changes) automatically via Wagmi.Use thetrack function to track custom user actions specific to your app. import { useFormo } from '@formo/analytics';
const HomePage = () => {
const analytics = useFormo();
useEffect(() => {
// Track a custom event
analytics.track('Swap Completed', { points: 100 });
}, [analytics]);
return <div>Welcome to the Home Page!</div>;
};
export default HomePage;
1. Install the Formo SDK
Install this script in the<head> tag of your website. Replace <YOUR_WRITE_KEY> with the SDK Write key found in your project settings:<script
src="https://cdn.formo.so/analytics@latest"
defer
onload="
window.formofy('<YOUR_WRITE_KEY>', {
ready: function(formo) {
formo.identify();
}
});
"
></script>
identify at the start of every session or page load links wallets to user sessions.To improve security, enable Subresource Integrity (SRI).2. Track custom events
Formo autocaptures events like page views, wallet connects, signatures, and transactions for you.For everything else, use thetrack function to track custom user actions specific to your app.<button type="button" onclick="window.formo.track('Swap Completed', { foo: 'bar' })">
Track event
</button>
Using Wagmi? Install with Wagmi instead for better event tracking.
1. Install the Formo SDK
npm install @formo/analytics --save
2. Use FormoAnalyticsProvider in your app
Wrap your React app in the provider provided by the SDK. // App.tsx (or App.js)
import { FormoAnalyticsProvider } from '@formo/analytics';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<FormoAnalyticsProvider writeKey="<YOUR_WRITE_KEY>">
<App />
</FormoAnalyticsProvider>
</React.StrictMode>
);
<YOUR_WRITE_KEY> with the SDK Write key found in your project settings.3. Identify users
Callidentify at the start of every session or page load to link a wallet address to a session.
import { useFormo } from "@formo/analytics";
import { useAccount } from "wagmi";
const HomePage = () => {
const { address } = useAccount();
const analytics = useFormo();
useEffect(() => {
if (address && analytics) {
analytics.identify({ address });
}
}, [address, analytics]);
}
4. Track custom events
Formo autocaptures events like page views, wallet connects, signatures, and transactions for you.For everything else, use thetrack function to track custom user actions specific to your app. import { useFormo } from '@formo/analytics';
const HomePage = () => {
const analytics = useFormo();
useEffect(() => {
// Track a custom event
analytics.track('Swap Completed', { points: 100 });
}, [analytics]);
return <div>Welcome to the Home Page!</div>;
};
export default HomePage;
Using Wagmi? Install with Wagmi instead for better event tracking.
1. Install the Formo SDK
npm install @formo/analytics --save
2. Use FormoAnalyticsProvider in your app
Create a new AnalyticsProvider.tsx client component.App Router
App Router
// AnalyticsProvider.tsx
'use client';
import { FormoAnalyticsProvider } from '@formo/analytics';
type FormoAnalyticsProviderProps = {
writeKey: string,
children: React.ReactNode,
};
// The provider component
export const AnalyticsProvider: FC<FormoAnalyticsProviderProps> = ({
writeKey,
children,
}) => {
return (
<FormoAnalyticsProvider writeKey={writeKey}>
{children}
</FormoAnalyticsProvider>
);
};
export default AnalyticsProvider;
AnalyticsProvider component: // app/layout.tsx
import { AnalyticsProvider } from './AnalyticsProvider';
export default function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
return (
<html lang='en'>
<body>
<AnalyticsProvider writeKey='YOUR_WRITE_KEY'>
Your Page Content
</AnalyticsProvider>
</body>
</html>
);
}
Pages Router
Pages Router
// AnalyticsProvider.tsx
import { FormoAnalyticsProvider } from "@formo/analytics";
type FormoAnalyticsProviderProps = {
writeKey: string;
children: React.ReactNode;
};
// The provider component
export const AnalyticsProvider: FC<FormoAnalyticsProviderProps> = ({
writeKey,
children,
}) => {
return (
<FormoAnalyticsProvider writeKey={writeKey}>
{children}
</FormoAnalyticsProvider>
);
};
export default AnalyticsProvider;
AnalyticsProvider component: // pages/_app.tsx
import AnalyticsProvider from "@/AnalyticsProvider";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return (
<AnalyticsProvider writeKey='YOUR_WRITE_KEY'>
<Component {...pageProps} />
</AnalyticsProvider>
);
}
<YOUR_WRITE_KEY> with the SDK Write key found in your project settings.3. Identify users
Callidentify at the start of every session or page load to link a wallet address to a session.import { useFormo } from "@formo/analytics";
import { useAccount } from "wagmi";
const HomePage = () => {
const { address } = useAccount();
const analytics = useFormo();
useEffect(() => {
if (address && analytics) {
analytics.identify({ address });
}
}, [address, analytics]);
}
4. Track custom events
Formo autocaptures events like page views, wallet connects, signatures, and transactions for you.For everything else, use thetrack function to track custom user actions specific to your app. import { useFormo } from '@formo/analytics';
const HomePage = () => {
const analytics = useFormo();
useEffect(() => {
// Track a custom event
analytics.track('Swap Completed', { points: 100 });
}, [analytics]);
return <div>Welcome to the Home Page!</div>;
};
export default HomePage;
Working example: with-angular. Also see the Angular section of the Web SDK page.
FormoAnalyticsProvider and useFormo() are React-only. Angular apps install Formo on the non-wagmi, non-React path: import the framework-agnostic FormoAnalytics.init() core from the @formo/analytics/core subpath (added in v1.30.1), wrap it in an injectable service, and connect wallets over the bare EIP-1193 provider (window.ethereum).1. Install the Formo SDK
Install the SDK (v1.30.1 or later) along with thebuffer polyfill (Angular’s esbuild build doesn’t auto-polyfill Node globals, but the SDK uses Buffer to decode signed-message payloads) and viem:pnpm add @formo/analytics@^1.30.1 buffer viem
pnpm add -D @ngx-env/builder
src/polyfills.ts:// src/polyfills.ts
import { Buffer } from 'buffer';
(globalThis as unknown as { Buffer?: typeof Buffer }).Buffer ??= Buffer;
angular.json. Importing from @formo/analytics/core (step 2) keeps React out of the dependency graph, so only viem needs to be allowlisted for CommonJS:// angular.json (build > options)
{
"polyfills": ["src/polyfills.ts"],
"allowedCommonJsDependencies": ["viem"]
}
2. Wrap FormoAnalytics.init() in an injectable service
Import from @formo/analytics/core — the root entry re-exports the React provider, which Angular doesn’t need:// src/app/services/formo-analytics.service.ts
import { Injectable } from '@angular/core';
import { FormoAnalytics } from '@formo/analytics/core';
import type { IFormoAnalytics, IFormoEventProperties } from '@formo/analytics/core';
@Injectable({ providedIn: 'root' })
export class FormoAnalyticsService {
private analytics: IFormoAnalytics | null = null;
async init(): Promise<void> {
if (typeof window === 'undefined') return;
const writeKey = import.meta.env.NG_APP_FORMO_WRITE_KEY;
if (!writeKey) return;
this.analytics = await FormoAnalytics.init(writeKey, {
tracking: true,
autocapture: { connect: true, disconnect: true, chain: true, signature: true, transaction: true },
});
}
identify(address: string): void {
void this.analytics?.identify({ address });
}
track(event: string, properties?: IFormoEventProperties): void {
void this.analytics?.track(event, properties);
}
}
NG_APP_FORMO_WRITE_KEY with your write key in .env. The NG_APP_* prefix is exposed to the client by @ngx-env/builder.3. Initialize before bootstrap
UseprovideAppInitializer so the SDK’s autocapture wraps window.ethereum before any wallet interaction can happen:// src/app/app.config.ts
import { ApplicationConfig, inject, provideAppInitializer } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { FormoAnalyticsService } from './services/formo-analytics.service';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAppInitializer(() => inject(FormoAnalyticsService).init()),
],
};
Don’t initialize from
ngOnInit — it runs after first render, leaving a race window where early wallet interactions are not captured.4. Identify users
Callidentify() once a wallet address is known. Angular Router’s pushState is already wrapped by the SDK, so page events are autocaptured on every route change — do not add a NavigationEnd subscription that calls formo.page() or you’ll double-count.import { Injectable, inject, signal } from '@angular/core';
import { FormoAnalyticsService } from './services/formo-analytics.service';
import type { Address } from 'viem';
@Injectable({ providedIn: 'root' })
export class WalletService {
private readonly formo = inject(FormoAnalyticsService);
readonly address = signal<Address | null>(null);
async connect(): Promise<void> {
const [account] = await window.ethereum!.request({ method: 'eth_requestAccounts' });
this.address.set(account);
this.formo.identify(account);
}
}
5. Track custom events
Formo autocaptures page views, wallet connect/disconnect, chain switches, signatures, and transactions. Usetrack() for app-specific actions:import { Component, inject } from '@angular/core';
import { FormoAnalyticsService } from './services/formo-analytics.service';
@Component({ /* ... */ })
export class Home {
private readonly formo = inject(FormoAnalyticsService);
onSwapCompleted(): void {
this.formo.track('Swap Completed', { points: 100 });
}
}
Code Examples
Example Apps
Working examples for Privy, Dynamic, React, and Next.js
Autocapture
The Formo 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 configure anything to track these events.Verification
To verify that the SDK is installed correctly, navigate to your site and open the network tab of the developer tools in your browser. Go to your browser’s Network tab and look for successful ‘raw_events’ request in the network console. Check that the request returns a 202 response status. (Note that it may take up to a minute due to the flush interval.) Events that are tracked correctly should also show up in the Activity page of your Formo workspace.SDK
Web SDK
HTML, React, Next.js
Mobile SDK
React Native
Server-side SDK
Typescript
FAQ
Which installation method should I use: Wagmi, React, Next.js, or HTML snippet?
Which installation method should I use: Wagmi, React, Next.js, or HTML snippet?
If your app uses Wagmi for wallet connections and its hooks for transactions, use the Wagmi integration. For React or Next.js apps without Wagmi, use the React integration. For static sites or non-React apps, use the HTML snippet. For mobile apps, use the React Native SDK.
Why are my transactions and signatures not being tracked?
Why are my transactions and signatures not being tracked?
When using the Wagmi integration, transaction and signature tracking requires your app to use the wagmi
useWriteContract hooks. Contract 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.How do I verify that the Formo SDK is installed correctly?
How do I verify that the Formo SDK is installed correctly?
After installing, visit your site and open Live View in the Formo dashboard. You should see your pageview appear in real-time. You can also check your browser’s Network tab for requests to
events.formo.so to inspect if events are sent successfully.Does the Formo SDK work on localhost during development?
Does the Formo SDK work on localhost during development?
The SDK skips tracking in localhost by default. For testing, you can enable local testing and logging in the SDK configuration.
How do I prevent ad blockers from blocking Formo?
How do I prevent ad blockers from blocking Formo?
Set up a reverse proxy to route Formo requests through your own domain. This prevents ad blockers from blocking events sent to Formo.
Support
Live support
Chat with our support team
Email our founder at yos@formo.so