@anvilco/browser
v0.1.0
Published
Anvil JavaScript SDK for browser clients
Downloads
110
Readme
@anvilco/browser
Browser SDK for Anvil. Handles OAuth 2.0 PKCE authentication and PDF filling for client-side JavaScript applications.
Installation
npm install @anvilco/browserNo framework required — works in any browser environment. If you're using React, @anvilco/react wraps this package with hooks and a provider.
Quick start
import { createClient, Scopes } from '@anvilco/browser'
const client = await createClient({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback',
scope: [Scopes.PDF_FILL, Scopes.OFFLINE_ACCESS],
})When to use this SDK
This SDK is for a specific use case: your end users have their own Anvil accounts and your app acts on their behalf — similar to "Sign in with Google." When they click login, they are redirected to Anvil, authenticate with their own Anvil credentials, and grant your app access.
This is the right model when you're building a product where users authenticate with their own Anvil accounts and your app accesses their organization's resources — templates, documents, and more — within the scopes they grant.
This is not the right model if you're looking to take action for your end users. If Anvil is an implementation detail of your product — your users just need to fill a PDF, sign a document, etc. — they should never see an Anvil login screen, and you don't need this SDK at all. Keep Anvil on your backend: your server holds an API key, calls Anvil on behalf of your users, and returns results to the client. Your frontend calls your backend using whatever auth you already have. For Node.js, use node-anvil or call the REST API directly with an API key.
Authentication
@anvilco/browser implements the OAuth 2.0 Authorization Code flow with PKCE — designed for public clients that cannot store a client secret. The flow has three steps: redirect the user to Anvil to authorize, handle the callback to exchange the code for tokens, then use the stored tokens to make API calls. Tokens are automatically refreshed when they expire if Scopes.OFFLINE_ACCESS was requested.
Redirect to Anvil
const url = await client.getAuthorizationUrl()
window.location.href = urlHandle the callback
On the page your redirectUri points to:
const handled = await client.handleAuth()
if (handled) {
// tokens stored, redirect to your app
}In environments where the redirect never navigates the page (Chrome extensions, Electron, tests), pass the redirect URL directly instead:
// e.g. Chrome extension using chrome.identity.launchWebAuthFlow
const redirectUrl = await launchWebAuthFlow(authUrl) // returns the full redirect URL
const handled = await client.handleAuthRedirect(redirectUrl)Check auth state
if (client.isAuthenticated()) {
// valid token in storage
}Logout
await client.logout()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.
const result = await client.fillPdf('your-cast-eid', {
data: { name: 'Alice', dob: '1990-01-01' },
})
if (result.errors) {
console.error(result.errors)
} else {
const url = URL.createObjectURL(result.data)
window.open(url)
}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 (third argument):
| Option | Type | Description |
| --------------- | ------------- | ----------------------------------- |
| signal | AbortSignal | Cancel the request |
| versionNumber | number | Fill a specific version of the cast |
const controller = new AbortController()
const result = await client.fillPdf(castEid, payload, {
signal: controller.signal,
versionNumber: 2,
})
// cancel an in-flight request from elsewhere (e.g. a cancel button, route change, or cleanup function)
controller.abort()API
createClient(options)
Returns a Promise that resolves to an AnvilClient. Options:
| Option | 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) |
AnvilClient
| Method | Returns | Description |
| -------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
| getAuthorizationUrl() | Promise<string> | Builds the Anvil authorization URL |
| handleAuth() | Promise<boolean> | Exchanges the auth code from window.location; returns true if handled |
| handleAuthRedirect(redirectUrl) | Promise<boolean> | Exchanges the auth code from a URL string; use when the page never navigates |
| isAuthenticated() | boolean | true if a valid (or refreshable) token exists |
| logout() | Promise<void> | Clears stored tokens |
| fillPdf(castEid, payload, options?) | Promise<RESTResponse<Blob>> | Fills a PDF and returns the result as a Blob |
| graphql(query, variables?, options?) | Promise<GraphQLResponse> | Executes a GraphQL query or mutation against the Anvil API |
| createCast(file, options?) | Promise<GraphQLResponse> | Uploads a PDF (URL string or Blob) to create a new cast (template) |
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'Token storage
By default tokens are stored in sessionStorage. Two backends are provided:
import { sessionStorageBackend, createMemoryBackend } from '@anvilco/browser'sessionStorageBackend— persists for the browser tab session (default)createMemoryBackend()— in-memory only, useful for testing or SSR hydration
You can supply any object that implements TokenStorage:
interface TokenStorage {
get(key: string): Promise<string | null>
set(key: string, value: string): Promise<void>
remove(key: string): Promise<void>
}All methods are async to support backends like chrome.storage.local, IndexedDB, or any other async store. The built-in backends wrap synchronous sessionStorage in Promise.resolve() to satisfy the interface.
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/