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

at-astro

v1.1.1

Published

An Astro integration for the AT Protocol, implementing OAuth flow and publishing an authenticated client to fetch and mutate records.

Readme

AT Astro

NPM Package Build Status Downloads Issues

An integration to build AT Protocol AppViews using Astro. This package implements the OAuth flow with Astro and exposes helpful utilities to get an authenticated ATProto client and manage sign in and sign out.

Note on Port

By default, Astro exposes the dev server using port 3000; however, OAuth redirects require a non-localhost URL, so I recommend running Astro with astro dev --host 127.0.0.1.

Installation

npm i at-astro
# or
yarn add at-astro
# or
pnpm i at-astro
# or
bun i at-astro

Then, in your astro.config.ts:

// Add this import
import atproto from "at-astro"

export default defineConfig({
  // Ensure site is defined
  site: "https://pixl.pics/",
  // Ensure you have an adapter set up (this example uses Cloudflare)
  adapter: cloudflare(),

  integrations: [
    // Add this integration to your Astro config
    atproto({
      // Add the OAuth scopes your app needs to access. Typically this is your app's lexicon namespace.
      scopes: ["repo:com.myapp.mylexicon"],
    }),
  ],
})

AT Astro detects runtimes that reject RequestInit.redirect: "error" and applies a scoped OAuth fetch compatibility patch automatically. Set patchRedirects: true or false only to force or disable that behavior for a runtime whose capability detection is inaccurate.

Usage

Routes

This package adds the following routes:

  • /oauth-client-metadata.json - OAuth2 client metadata
  • /oauth/login - OAuth2 login route
  • /oauth/callback - OAuth2 callback route
  • /oauth/logout - Sign out route

To add sign in, create a sign in page and add a standard HTML form that submits to /oauth/login:

<form action="/oauth/login" method="post">
  <label>
    Handle
    <input name="handle" placeholder="you.bsky.social" required />
  </label>
  <button>Sign in</button>
</form>

For an unstyled handle typeahead, compose the optional components around the same native form:

---
import HandleField from "at-astro/components/HandleField"
import HandleInput from "at-astro/components/HandleInput"
import HandleOption from "at-astro/components/HandleOption"
import HandleOptions from "at-astro/components/HandleOptions"
---

<form action="/oauth/login" method="post">
  <HandleField>
    <label>
      Handle
      <HandleInput autocomplete="off" placeholder="you.bsky.social" required />
    </label>

    <HandleOptions>
      <HandleOption>
        <span data-at-field="displayName"></span>
        <span>@<span data-at-field="handle"></span></span>
      </HandleOption>
    </HandleOptions>
  </HandleField>

  <button>Sign in</button>
</form>

HandleField uses the integration's configured publicEndpoint by default. Pass endpoint to query a different service for this field:

<HandleField endpoint="https://another-appview.example.com">
  <!-- HandleInput and HandleOptions -->
</HandleField>

The single child of HandleOptions is an inert native template that is cloned for each suggestion. Bind returned actor fields with data-at-field="did", data-at-field="handle", data-at-field="displayName", or data-at-field="avatar"; the avatar binding must be placed on an <img>. HandleField reports data-state="idle", loading, success, empty, or error on its root element so application CSS can respond without coupling to the implementation. The components add no styles, and the submitted value remains an ordinary input[name="handle"]. Suggestions are optional and do not restrict which handles can be submitted.

Sign out is just as simple:

<form action="/oauth/logout" method="post">
  <button>Sign out</button>
</form>

It supports GET requests as well, so an alternative would be:

<a href="/oauth/logout">Sign out</a>

Client

After OAuth, you will have access to an authenticated ATProto client using the getATProtoClient function, which is available on the Astro locals object. In an Astro component, you can access it like this:

---
const { client, did, handle } = await Astro.locals.getATProtoClient()
---

Outside of components, there are several APIs (middleware, actions, etc) that offer access to the locals object as well.

The did will be null if the user is not authenticated; in that case, the client will be an unauthenticated client that can be used for read-only operations. Use did == null as your guard for authentication.