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

strapi-plugin-ai-assistant

v0.2.0

Published

Draft, rewrite, translate, and SEO-assist Strapi content with OpenAI, Claude, or Gemini.

Readme

strapi-plugin-ai-assistant

Draft, rewrite, translate, and SEO-assist Strapi 5 content from the admin, using OpenAI, Claude, or Gemini.

The plugin reads your content-type schema and fills titles, teasers, rich text (Markdown, HTML, Blocks, or TipTap), SEO fields, dates, and image alt text. Editors can also create a draft from a public URL, a Word/PDF upload, or a short brief.

Requirements

  • Strapi 5.13+ (homepage widget and Content Manager side panel)
  • Node 20+
  • An API key for at least one provider: OpenAI, Anthropic (Claude), or Google Gemini

Install

npm install strapi-plugin-ai-assistant

Rebuild the admin panel and restart Strapi:

npm run build
npm run develop

Strapi loads installed plugins automatically. If you need to toggle it explicitly, add this to config/plugins.ts:

export default {
  'ai-assistant': {
    enabled: true,
  },
};

Configure

Open Settings → AI Assistant.

  1. Paste an API key for OpenAI, Claude, and/or Gemini. Keys are stored encrypted with ENCRYPTION_KEY or, if that is unset, the first value in APP_KEYS. If you rotate that secret, saved keys become unreadable and must be pasted again.
  2. Pick the default provider and model. Editors can override the model per request.
  3. Write a voice prompt for your brand. The default is a short generic editorial prompt — replace it with yours.
  4. Enable the content types the assistant may edit, and exclude fields it should leave alone.

Optional environment fallbacks (the Settings UI is enough for most sites):

AI_ASSISTANT_PROVIDER=openai
OPENAI_API_KEY=
OPENAI_MODEL=gpt-5.6-luna
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-sonnet-5
GEMINI_API_KEY=
GEMINI_MODEL=gemini-3.7-flash
ENCRYPTION_KEY=  # recommended; otherwise APP_KEYS is used

Permissions

After install, grant these in Settings → Administration panel → Roles:

| Action | Who needs it | | --- | --- | | Use AI Assistant | Editors who generate, translate, or create drafts | | Configure AI Assistant | Admins who manage keys, voice, and content types |

Super Admin already has both.

What it can do

From the Content Manager side panel, Create with AI in the menu, or the homepage widget:

  • Generate or rewrite copy for the current entry, in a locale you pick
  • Write SEO title / description / keywords
  • Draft from a public URL or a .docx / .pdf / .txt / .md file
  • Improve, shorten, or retitle
  • Generate image alt text
  • Translate into other locales (Internationalization)
  • Create a new draft entry mapped onto your schema

It skips relations, dynamic zones (except a required zone it can seed), passwords, and most custom fields it does not understand. TipTap JSON, Strapi Blocks, and HTML rich text are all supported.

Search Assistant (semantic search)

The plugin indexes published entries into a vector table in Strapi's own database and exposes POST /api/ai-assistant/search. A natural-language query is embedded and ranked by cosine similarity against those vectors.

This is not Meilisearch/Algolia (typos, SKUs, facets). Ranking is embeddings + cosine. A lexical fallback still keeps an entry if the query (or every significant word) appears in the indexed text, so a short keyword like transport can hit an article about medical transport even when cosine is below the tolerance. Intent prefixes (quiero buscar, search for, …) are always stripped. Rewrite query with AI (off by default) produces two short phrases — the user’s language plus a second-language variant — and embeds both, so the index language does not have to match the query. Pass locale when the type is i18n; that locale is a hint for the second phrase, not a reason to drop the first. Answer with AI (off by default) then writes a short grounded paragraph from the top hits; it does not rerank.

No external vector DB is required.

Enable

Open Settings → AI Assistant → Search:

  1. Turn on Enable Search Assistant. You need an OpenAI or Gemini API key. Anthropic has no embeddings API, so Claude cannot index or search.
  2. Pick the embedding provider and model (defaults: text-embedding-3-small for OpenAI, gemini-embedding-001 for Gemini).
  3. Pick a tolerance (strict / normal / loose — cosine thresholds ~0.75 / 0.60 / 0.45). Weaker models often score unrelated dummy text around 0.6, so Normal can look noisy; Strict is safer until you have real copy.
  4. Optionally turn on Rewrite query with AI. Requires a chat API key (OpenAI, Claude, or Gemini) on the main Settings page. Conversational queries become a short phrase in the user’s language plus a second-language variant; both are embedded. If the rewrite fails, search uses the stripped original. Leave it off for a direct embed of what the user typed.
  5. Optionally turn on Answer with AI. After ranking, the same chat model writes a short paragraph from the top hits, in the user’s language. Search results do not change. If the model fails or times out, the response has no answer field.
  6. For each content type, tick Enable for search and untick fields you do not want indexed. Dynamic zones and repeatable components are one row each and cover every text field inside their blocks.

Save before Reindex. The queue uses the saved provider/model, not the unsaved dropdown. Wait until the page shows Idle.

Indexing runs in the background. You can re-index one type or everything.

One embedding space

The same saved embedding model embeds both the documents and the query. OpenAI and Gemini vectors are not comparable.

  • Do not switch provider in the UI and search without saving and reindexing.
  • After a model change, leftover chunks from the previous provider:model are ignored. Search against an empty matching set returns { "results": [], "total": 0 } (not 503, unless the whole table is empty).
  • Changing the saved model triggers a full reindex. Finish that job before querying.

What is indexed

Text only: strings, rich text (Markdown, HTML, Blocks, TipTap), and text inside dynamic zones / components you left ticked. Media, relations, and passwords are skipped — images are not described or embedded.

Indexing populates components and dynamic zones (not media/relations). Nested titles such as card.Title are included in the chunk text and used as the hit title. Later chunks of a long article are prefixed with that title so it is not lost after the first split.

Endpoint

POST /api/ai-assistant/search
Content-Type: application/json

{
  "query": "remote work regulation in 2024",
  "uid": "api::article.article",
  "from": "2024-01-01",
  "to": "2024-12-31",
  "limit": 10
}

| Field | Required | Notes | | --- | --- | --- | | query | yes | Natural language. Intent prefixes are stripped. If Rewrite query with AI is on, the chat model may shorten it before embedding. | | uid | no | Must be search-enabled. Omit to search all enabled types. | | from / to | no | Filter on the entry's publishedAt. | | locale | no | Only for i18n types. Chunks from types without Internationalization are stored with an empty locale. Sending "locale": "en" against those types returns zero hits. Omit locale unless the content type is localized. | | limit | no | Number of results to return. Default 10, hard cap 50. There is no pagination (offset / cursor). |

Response:

{
  "results": [
    {
      "uid": "api::article.article",
      "documentId": "…",
      "locale": "en",
      "slug": "remote-work-2024",
      "title": "Remote work rules updated in 2024",
      "publishedAt": "2024-03-12T…",
      "score": 0.78,
      "entry": { }
    }
  ],
  "threshold": 0.6,
  "total": 3,
  "query": "quiero buscar remote work",
  "rewrittenQuery": "trabajo remoto",
  "rewrittenQueryAlt": "remote work",
  "answer": "The 2024 remote-work rules apply to employees who work from home more than three days a week."
}
  • results — at most limit (capped at 50), one hit per document (best chunk).
  • total — how many documents passed the cosine threshold before the cap. If total is 80 and limit is 10, you get 10 items; the rest are not listed and cannot be paged.
  • threshold — the tolerance in effect.
  • query — the string the client sent.
  • rewrittenQuery — primary phrase that was embedded (after strip and optional LLM rewrite). Same as query when nothing changed.
  • rewrittenQueryAlt — optional second-language phrase, also embedded. Omitted when unused.
  • answer — optional short paragraph grounded in the top hits. Present only when Answer with AI is on and the chat model succeeded.
  • Only published entries from search-enabled types.

Errors: 400 (bad input / search disabled / type not enabled), 429 (rate limit — 30 req/min per token or IP), 503 (index table empty — run Reindex). An empty results array with total: 0 means the table has chunks but none matched the filters, model tag, or threshold — not that search is “down”.

Auth

The route is registered as content-api with auth: false: it is callable without an API token. Rate limiting still applies. Tighten this in your app (API tokens, Users & Permissions, or a proxy) if the index should not be public.

How it works

  1. On publish/update of an enabled type, the plugin extracts the ticked text fields, splits them into ~1.8k-character chunks with a small overlap, and embeds each chunk with the saved embedding model.
  2. Vectors live in plugin::ai-assistant.search-chunk (hidden — not in Content Manager or the Content-Type Builder), tagged with provider:model.
  3. On search, intent prefixes are stripped. If Rewrite query with AI is on, the chat model may return a short phrase in the user’s language and a second-language variant (on failure, the stripped original is used). Unique phrases are embedded with the saved embedding model. SQL keeps chunks whose embeddingModel matches and that pass uid / date / locale filters. Survivors are ranked by the best cosine across those phrases. Chunks whose text already contains a phrase are kept even when cosine is below the tolerance, then hydrated into full published entries (components populated). If Answer with AI is on, the chat model writes a short paragraph from the top ranked chunks; ranking is unchanged.

Unpublish/delete removes those chunks. Turning off a content type or the global toggle purges its chunks. Changing the embedding model reindexes everything.

Document uploads

Word and PDF parsing ship with the plugin. Uploads go to the server as multipart files (not JSON), up to 6 MB. Extracted text is capped at about 12 000 characters.

| Package | Used for | Installed with the plugin | | --- | --- | --- | | mammoth | Word (.docx) text | Yes | | jszip | Images embedded in .docx | Yes | | pdf-parse | PDF text | Yes | | unpdf | Images inside PDFs | Yes | | sharp | Encoding those PDF images as JPEG | Optional — see below |

Notes:

  • .doc (Word 97–2003) is not supported. Save as .docx or PDF.
  • Scanned PDFs with no text layer cannot be read. Copy the text into Brief, or export a text PDF.
  • Google Docs: share with Anyone with the link, or File → Download → .docx / .pdf and upload the file.
  • If an image field is enabled for the content type, Create with AI / From source uploads the first usable image from the URL or document into the Media Library.

sharp (PDF cover images)

Strapi already depends on sharp for the Media Library, so PDF image encoding usually works with no extra install. This plugin lists sharp as an optional dependency for package managers that do not hoist Strapi’s copy (for example pnpm).

If Settings → AI Assistant reports that sharp is missing, install it on the Strapi app (not only this plugin):

npm install sharp

Without sharp, Word/PDF text still works. Cover images from PDFs may be skipped; images from .docx (word/media) still work.

Internationalization

Translate requires the Strapi Internationalization plugin (bundled with Strapi 5) with at least two locales. Enable i18n on the content type. Translations are saved as drafts; media and dates are copied, not rewritten.

Generation uses a Generate in locale picker (the default locale is selected first). The model is instructed to write in that language, including Create with AI drafts.

TipTap and other rich text

  • Markdown (richtext) and Blocks work with no extra plugin.
  • TipTap (JSON doc) is supported when the field’s customField looks like TipTap / a rich-text custom field. Install that custom-field plugin in your Strapi app if you use it. Applying a preview syncs TipTap editors and the other edit-view fields (including components in the right sidebar) from the form state.

Local development

This package is built with the Strapi Plugin SDK.

npm install
npm run watch:link

In a Strapi 5.13+ app:

npx yalc add --link strapi-plugin-ai-assistant
npm install
npm run develop
npm run build
npm run verify

Publish

  1. Upload logo.png (512×512) on the Marketplace form.
  2. npm login and npm publish --access public.
  3. Submit the npm URL on the Strapi Marketplace (guidelines).

Issues

Open an issue at github.com/cnp1984/strapi-plugin-ai-assistant/issues. Include your Strapi version, the content-type UID, and whether the request used a URL, a document, or a brief.

License

MIT