@anvilco/react
v0.1.1
Published
Anvil React SDK
Downloads
256
Readme
@anvilco/react
React SDK for Anvil. Provides hooks and a provider for OAuth authentication and PDF filling. Wraps @anvilco/browser with idiomatic React patterns.
Installation
npm install @anvilco/reactRequires React 17+ as a peer dependency.
Quick start
Wrap your app with AnvilProvider once at the root — the same pattern as React Query or Apollo. This creates a single shared client available to all hooks anywhere in the tree.
import { AnvilProvider, Scopes } from '@anvilco/react'
function App() {
return (
<AnvilProvider
clientId="your-client-id"
redirectUri="https://yourapp.com/callback"
scope={[Scopes.PDF_FILL, Scopes.OFFLINE_ACCESS]}
>
<YourApp />
</AnvilProvider>
)
}If only part of your app needs Anvil, you can scope the provider to that subtree instead.
Authentication
@anvilco/react implements the OAuth 2.0 Authorization Code flow with PKCE. The flow has three steps:
- Redirect — send the user to Anvil's authorization page
- Callback — Anvil redirects back to your
redirectUriwith a short-lived code; exchange it for tokens - Use — tokens are stored automatically and refreshed when they expire
Step 1 — trigger login
import { useAnvilAuth } from '@anvilco/react'
function LoginButton() {
const { isAuthenticated, isLoading, login, logout } = useAnvilAuth()
if (isLoading) return null
return isAuthenticated ? (
<button onClick={logout}>Logout</button>
) : (
<button onClick={login}>Login with Anvil</button>
)
}Step 2 — handle the callback
Set up a route matching your redirectUri. useAnvilCallback reads the ?code= param from the URL, exchanges it for tokens, then calls onSuccess so you can navigate the user to their destination.
import { useAnvilCallback } from '@anvilco/react'
function CallbackPage() {
useAnvilCallback({
onSuccess: () => navigate('/dashboard'),
onError: (err) => navigate('/login?error=1'),
})
return <p>Completing sign in...</p>
}Token refresh
Access tokens expire. To get long-lived sessions, request the OFFLINE_ACCESS scope — this gives you a refresh token and the client will automatically exchange it for a new access token when needed, without prompting the user again.
<AnvilProvider
clientId="your-client-id"
redirectUri="https://yourapp.com/callback"
scope={[Scopes.PDF_FILL, Scopes.OFFLINE_ACCESS]}
>
<App />
</AnvilProvider>Without OFFLINE_ACCESS, the session ends when the access token expires and the user will need to log in again.
Filling a PDF
castEid is Anvil's internal identifier for a PDF template — the same value referred to as pdfTemplateEid in the Anvil dashboard and REST docs.
import { useFillPdf } from '@anvilco/react'
function FillButton({ castEid }) {
const { fillPdf, loading, error, data } = useFillPdf(castEid)
const handleClick = () => fillPdf({ data: { name: 'Alice', dob: '1990-01-01' } })
if (loading) return <p>Generating PDF...</p>
if (error) return <p>Error: {error.message}</p>
if (data)
return (
<a href={URL.createObjectURL(data)} target="_blank">
Open PDF
</a>
)
return <button onClick={handleClick}>Fill PDF</button>
}API
<AnvilProvider>
| Prop | Type | Required | Description |
| ------------- | -------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| clientId | string | Yes | Your Anvil OAuth application client ID |
| redirectUri | string | Yes | URI Anvil redirects to after auth |
| scope | Scope \| Scope[] | No | OAuth scope(s) to request (use Scopes constants) |
| baseUrl | string | No | Override the Anvil API base URL |
| authBaseUrl | string | No | Override the base URL for auth endpoints only (defaults to baseUrl if omitted) |
| storage | TokenStorage | No | Custom token storage (default: sessionStorage) |
| onLogin | (authUrl: string) => Promise<string> | No | Custom login handler — receives the auth URL, must return the redirect URL. Use this in environments where window.location redirects don't work (e.g. Chrome extensions). Overridable per call site via useAnvilAuth({ onLogin }). |
useAnvilAuth(options?)
Options:
onLogin—(authUrl: string) => Promise<string>— overrides the provider-levelonLoginfor this call site
Returns { isAuthenticated, isLoading, login, logout }.
isAuthenticated—boolean, reactive to login/logoutisLoading—boolean,truewhile the client is still initializinglogin()— triggers the login flow (redirect or customonLogin)logout()—Promise<void>, clears the stored token
Chrome extension usage
In a Chrome extension the side panel never navigates, so window.location redirects don't work. Pass onLogin at the provider level to use chrome.identity.launchWebAuthFlow instead — login() then works identically for all components:
function launchWebAuthFlow(authUrl: string): Promise<string> {
return new Promise((resolve, reject) => {
chrome.identity.launchWebAuthFlow({ url: authUrl, interactive: true }, (redirectUrl) => {
if (chrome.runtime.lastError || !redirectUrl) {
reject(new Error(chrome.runtime.lastError?.message ?? 'Auth failed'))
} else {
resolve(redirectUrl)
}
})
})
}
;<AnvilProvider
clientId="your-client-id"
redirectUri={`https://${chrome.runtime.id}.chromiumapp.org/callback`}
onLogin={launchWebAuthFlow}
>
<SidePanel />
</AnvilProvider>useAnvilClient()
Returns the AnvilClient instance from the nearest AnvilProvider, or null if the client hasn't initialized yet. Use this when you need direct access to the client for GraphQL queries or createCast calls that aren't covered by a dedicated hook.
import { useAnvilClient } from '@anvilco/react'
function MyComponent() {
const client = useAnvilClient()
const handleQuery = async () => {
if (!client) return
const result = await client.graphql('{ me { eid } }')
console.log(result.data)
}
return <button onClick={handleQuery}>Query</button>
}useAnvilCallback(options?)
Call on the page that receives the OAuth redirect. Options:
onSuccess()— called after tokens are successfully exchangedonError(err)— called if the exchange fails
useFillPdf(castEid)
Returns { fillPdf, abort, loading, error, data }.
fillPdf(payload, options?)— triggers the fill request; cancels any previous in-flight request automaticallyabort()— cancel the current in-flight requestloading—boolean, true while the request is in flighterror—Error | nulldata—Blob | null, the filled PDF
The hook manages cancellation automatically — in-flight requests are aborted on unmount and when fillPdf is called again. Use abort when you need explicit control, such as a cancel button:
function FillButton({ castEid }) {
const { fillPdf, abort, loading, data } = useFillPdf(castEid)
return (
<>
<button onClick={() => fillPdf({ data: { name: 'Alice' } })}>Fill PDF</button>
{loading && <button onClick={abort}>Cancel</button>}
{data && <a href={URL.createObjectURL(data)}>Open PDF</a>}
</>
)
}FillPdfPayload fields:
| Field | Type | Required | Description |
| --------------------------- | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| data | FillPdfData | Yes | Field values — see below |
| title | string | No | Override the PDF title |
| fontSize | number | No | Default font size in points for all fields (5–30) |
| textColor | string | No | Default text color for all fields (CSS hex, e.g. #FF0000) |
| fontFamily | string | No | Default font family for all fields |
| borderColor | string | No | Default border color for field bounding boxes (CSS hex) |
| verticalAlignment | 'top' \| 'middle' \| 'bottom' | No | Default vertical alignment for text within fields |
| defaultCheckboxCharacter | string | No | Character used to represent a checked checkbox |
| defaultMakeCheckboxSquare | boolean | No | Render checkboxes as squares |
| useInteractiveFields | boolean | No | Render as interactive AcroForm fields instead of flattened text (requires paid plan) |
| drawDebugBoxes | boolean | No | Draw visible bounding boxes around each field for layout debugging |
| stripAnnotations | boolean | No | Remove all PDF annotations from the output |
| defaultReadOnly | boolean | No | Default all interactive fields to read-only (only applies when useInteractiveFields is true) |
data accepts a single object keyed by field ID or alias, or an array of objects for multi-page repeating templates. Each value can be a primitive or a value object with per-field styling:
// flat fill
{ data: { name: 'Alice', age: 30, agreed: true } }
// per-field styling override
{ data: { name: { value: 'Alice', fontSize: 14, textColor: '#0000FF' } } }
// repeating pages
{ data: [{ name: 'Alice' }, { name: 'Bob' }] }fillPdf options:
| Option | Type | Description |
| --------------- | -------- | ----------------------------------- |
| versionNumber | number | Fill a specific version of the cast |
Scopes
Constants for OAuth scopes:
Scopes.PDF_FILL // 'pdf:fill'
Scopes.PDF_GENERATE // 'pdf:generate'
Scopes.OFFLINE_ACCESS // 'offline_access'
Scopes.CAST_WRITE // 'cast:write'
Scopes.MCP_FULL // 'mcp:full'Development
yarn build # compile to dist/
yarn test # run tests
yarn test:watch # run tests in watch mode
yarn typecheck # type check without building
yarn lint # lint src/