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

@freshjuice/astro-search-plugin

v1.0.1

Published

Framework-agnostic, type-safe search for Astro 5+ powered by Orama. Build the index at build time, query it client-side, drop in a Cmd+K palette as a web component (vanilla, Vue, Svelte, Solid…) or React component. Apache-2.0, no runtime CDN, no telemetry

Downloads

207

Readme

@freshjuice/astro-search-plugin

Framework-agnostic, type-safe search for Astro 5+, powered by Orama. Build the index at build time, query it client-side, drop in a Cmd+K command palette as a web component (works in Vue, Svelte, Solid, Lit, Preact, vanilla HTML — anywhere), or as a React component. Apache-2.0. No runtime CDN. No telemetry.

npm version License: Apache 2.0

Why this exists

The official @orama/plugin-astro declares astro: ^2.0.4 as a peer dependency. On Astro 5 / 6 this either fails to install or, with --legacy-peer-deps, pulls a shadow copy of Astro 2 alongside your real Astro version — the plugin doesn't actually integrate with your build.

This package fills the gap: a small, focused plugin built on the same Orama core, designed for modern Astro, with explicit TypeScript types and a UI that doesn't lock you into one framework.

What you get

| | | |---|---| | 🔍 Build-time indexing | One JSON index built from your content collections, no runtime CDN | | 🧱 Framework-agnostic UI | <astro-search-palette> web component works in Astro, Vue, Svelte, Solid, Lit, Preact, vanilla HTML | | ⚛️ React adapter | <SearchPalette> / <SearchBox> for React-heavy apps that want JSX-native props | | 🔧 Programmatic core | Bring your own UI — loadIndex / searchIndex / groupResults exposed directly | | 🧩 Multi-collection search | Index blog + tools + docs in one go, filter by type, tag, etc. | | 🎯 Faceted filters | First-class — Orama's where: { type: 'blog' } works out of the box | | 📦 Tiny client runtime | ~10kb gzipped + your index JSON (~1-2MB for 250 pages) | | 🛡️ No SSR pitfalls | Components defer Orama load until after hydration | | 📜 Apache-2.0 | Full sources, NOTICE attribution to Orama, no telemetry, no signups |

Install

npm install @freshjuice/astro-search-plugin
# Astro is the only required peer
npm install astro

# Optional — only if you use the React adapter
npm install react react-dom

Requires Astro 5+. ESM-only. React is a fully optional peer dependency.

Subpath exports

| Import path | What it ships | When to use | |---|---|---| | @freshjuice/astro-search-plugin | Type-only + core re-exports | Type imports, programmatic helpers | | @freshjuice/astro-search-plugin/build | buildSearchIndex (Node) | In your Astro endpoint, server side | | @freshjuice/astro-search-plugin/core | loadIndex, searchIndex, groupResults … | Build your own UI in any framework | | @freshjuice/astro-search-plugin/element | Registers <astro-search-palette> custom element | Vanilla, Astro, Vue, Svelte, Solid, Lit, Preact | | @freshjuice/astro-search-plugin/react | <SearchPalette>, <SearchBox> React components | React-first projects | | @freshjuice/astro-search-plugin/styles.css | Default stylesheet | Always — used by both /element and /react |

Quickstart

1. Build the index

Create a static endpoint that emits the index. The plugin gives you the helper — you decide what gets indexed, from which collection, with what schema.

// src/pages/search-index.json.ts
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { buildSearchIndex } from "@freshjuice/astro-search-plugin/build";

export const GET: APIRoute = async () => {
  const blog = await getCollection("blog", ({ data }) => !data.draft);
  const tools = await getCollection("tools");

  const index = await buildSearchIndex({
    schema: {
      type: "string",
      title: "string",
      desc: "string",
      url: "string",
      tags: "string[]",
    },
    documents: [
      ...blog.map((p) => ({
        id: p.id,
        type: "blog",
        title: p.data.title,
        desc: p.data.desc,
        url: `/blog/${p.id}/`,
        tags: p.data.tags,
      })),
      ...tools.map((t) => ({
        id: t.id,
        type: "tool",
        title: t.data.title,
        desc: t.data.desc,
        url: `/tools/${t.id}/`,
        tags: [],
      })),
    ],
  });

  return new Response(JSON.stringify(index), {
    headers: { "Content-Type": "application/json" },
  });
};

2. Drop the palette into your layout — pick your flavor

Vanilla / Astro / any framework — web component

---
// src/layouts/BaseLayout.astro
import "@freshjuice/astro-search-plugin/styles.css";
---

<html>
  <body>
    <slot />

    <astro-search-palette
      index-url="/search-index.json"
      shortcut="mod+k"
      placeholder="Search…"
      group-by="type"
    ></astro-search-palette>

    <script>
      // Side-effect import: registers <astro-search-palette> globally
      import "@freshjuice/astro-search-plugin/element";
    </script>
  </body>
</html>

That's it. Cmd+K anywhere on the site opens a search modal.

Vue 3

<template>
  <astro-search-palette
    index-url="/search-index.json"
    placeholder="Search…"
    group-by="type"
  />
</template>

<script setup lang="ts">
import "@freshjuice/astro-search-plugin/element";
import "@freshjuice/astro-search-plugin/styles.css";
</script>

Svelte

<script lang="ts">
  import "@freshjuice/astro-search-plugin/element";
  import "@freshjuice/astro-search-plugin/styles.css";
</script>

<astro-search-palette
  index-url="/search-index.json"
  placeholder="Search…"
  group-by="type"
/>

React

import { SearchPalette } from "@freshjuice/astro-search-plugin/react";
import "@freshjuice/astro-search-plugin/styles.css";

export default function Layout() {
  return (
    <SearchPalette
      indexUrl="/search-index.json"
      placeholder="Search…"
      groupBy="type"
    />
  );
}

In Astro, hydrate it with client:idle (or client:load):

---
import { SearchPalette } from "@freshjuice/astro-search-plugin/react";
import "@freshjuice/astro-search-plugin/styles.css";
---
<SearchPalette client:idle indexUrl="/search-index.json" />

3. Open it programmatically

The web component listens to global window events. Open it from anywhere:

window.dispatchEvent(new CustomEvent("astro-search:open"));
window.dispatchEvent(new CustomEvent("astro-search:close"));
window.dispatchEvent(new CustomEvent("astro-search:toggle"));

4. Override navigation

Listen to the astro-search:select event before navigation. Call preventDefault() to take over (e.g. for SPA routing or analytics):

document.querySelector("astro-search-palette").addEventListener(
  "astro-search:select",
  (e) => {
    e.preventDefault();
    myRouter.push(e.detail.document.url);
  },
);

In React, pass onSelect={(doc) => router.push(doc.url)}.

API reference

<astro-search-palette> attributes

| Attribute | Default | Description | |---|---|---| | index-url | — (required) | URL to fetch the serialized index JSON | | shortcut | "mod+k" | Keyboard combo. mod = ⌘ on macOS, Ctrl elsewhere. Or "/", "ctrl+k", etc. | | placeholder | "Search..." | Input placeholder text | | result-limit | 10 | Maximum results returned per query | | group-by | unset | Field name to group results by (e.g. type) |

<astro-search-palette> events

| Event | Detail | Cancelable | |---|---|---| | astro-search:open | — | no — listened to | | astro-search:close | — | no — listened to | | astro-search:toggle | — | no — listened to | | astro-search:select | { document } | yes — call preventDefault() to override navigation |

<SearchPalette> (React) props

interface SearchPaletteProps {
  indexUrl: string;
  shortcut?: string;          // Default: "mod+k"
  placeholder?: string;       // Default: "Search..."
  resultLimit?: number;       // Default: 10
  searchableProperties?: string[];
  filter?: Record<string, unknown>; // Orama where clause
  groupBy?: string;
  renderResult?: (doc: SearchDocument) => ReactNode;
  onSelect?: (doc: SearchDocument) => void;
}

<SearchBox> (React)

Standalone search input with inline dropdown — no modal. Useful for sidebar or hero placements.

import { SearchBox } from "@freshjuice/astro-search-plugin/react";

<SearchBox indexUrl="/search-index.json" autoFocus />

Programmatic API — /core

import {
  loadIndex,
  searchIndex,
  groupResults,
  matchesShortcut,
  navigateToDocument,
} from "@freshjuice/astro-search-plugin/core";

const db = await loadIndex("/search-index.json");
const results = await searchIndex(db, "what is paid media", {
  limit: 20,
  where: { type: "blog" },
});

buildSearchIndex(config)/build

Server-side. Builds an Orama index and serializes it to JSON.

import { buildSearchIndex, type SearchSchema } from "@freshjuice/astro-search-plugin/build";

const index = await buildSearchIndex({
  schema: SearchSchema,        // Orama schema definition
  documents: SearchDocument[], // your records
  language: "english",         // Default: "english"
});

Styling

Default classes use the astro-search- prefix. Override anything by re-defining them after importing styles.css:

.astro-search-modal {
  border-radius: 4px;          /* sharper corners */
  background: var(--my-card);
}
.astro-search-result[data-selected="true"] {
  background: var(--my-accent);
}

The bundled stylesheet ships with a prefers-color-scheme: dark block — drop it in and dark mode just works.

How it differs

vs. @orama/plugin-astro

| | @freshjuice/astro-search-plugin | @orama/plugin-astro | |---|---|---| | Astro support | 5, 6 (current) | 2.x (peer dep ^2.0.4 as of v3.1.18) | | Source available | Yes (this repo) | Source not in their public monorepo at time of writing | | Build approach | Astro endpoint pattern (you control schema) | Auto-magic build hook | | TypeScript types | Strict, exported, schema-typed | Limited | | UI | Web component + React | React only | | Bundle size | ~10kb client + ~1MB index | similar |

vs. Pagefind

Pagefind is the de-facto standard for Astro static-site search and powers Astro Starlight. Use Pagefind if you want zero-config drop-in search with an opinionated UI, you're indexing a blog/docs site without complex filtering, and you don't need programmatic control. Use this plugin if you want type-safe schemas, faceted search out of the box, multi-collection unified results, multiple framework targets, or the option to add vector/hybrid search later.

Trademark / Affiliation Disclaimer

This package is not affiliated with, endorsed by, or sponsored by OramaSearch Inc. "Orama" is a trademark of OramaSearch Inc.; this package references the name solely to describe the search engine it integrates with, under nominative fair use.

For the official Orama Astro integration see @orama/plugin-astro. For the Orama core engine itself see @orama/orama.

Huge respect to the Orama team for the excellent core engine.

Contributing

Issues and PRs welcome. This is an OSS package by FreshJuice, maintained casually but seriously.

git clone https://github.com/freshjuice-dev/astro-search-plugin.git
cd astro-search-plugin
npm install
npm run build       # Build to dist/
npm run dev         # Watch mode
npm run typecheck   # tsc --noEmit

License

Apache-2.0 · See NOTICE for full attributions.

Built by FreshJuice — developer studio that ships standards, not products. Free tools, 0 marketing emails, all open source.