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

Requires 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:

  1. Redirect — send the user to Anvil's authorization page
  2. Callback — Anvil redirects back to your redirectUri with a short-lived code; exchange it for tokens
  3. 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-level onLogin for this call site

Returns { isAuthenticated, isLoading, login, logout }.

  • isAuthenticatedboolean, reactive to login/logout
  • isLoadingboolean, true while the client is still initializing
  • login() — triggers the login flow (redirect or custom onLogin)
  • 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 exchanged
  • onError(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 automatically
  • abort() — cancel the current in-flight request
  • loadingboolean, true while the request is in flight
  • errorError | null
  • dataBlob | 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/