npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/browser

No 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 = url

Handle 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/