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

@sparrowengg/payloadcms-plugin-interlink-content

v0.1.15

Published

A Payload CMS plugin for adding AI interlinking capabilities to rich text fields

Readme

@sparrowengg/payloadcms-plugin-interlink-content

Internal Payload CMS plugin of SurveySparrow for adding AI interlinking capabilities to rich text fields.

Requirements

This package has the following peer dependencies. Make sure you have these:

"payload": "^3.33.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@payloadcms/richtext-lexical": "^3.33.0",
"@payloadcms/ui": "^3.33.0"

Installation

1. Install the package

pnpm add @sparrowengg/payloadcms-plugin-interlink-content

2. Environment Variables

In your .env file, add the following environment variables:

NEXT_PUBLIC_SERVER_URL=https://www.yoursite.com
NEXT_PUBLIC_SITEMAP_URL=https://www.yoursite.com/sitemap.xml
NEXT_PUBLIC_PINECONE_TEAM=<Get value from AI team>
NEXT_PUBLIC_AI_HYPERLINK_API=<Get value from AI team>

Note: Sitemap index URL is recommended for NEXT_PUBLIC_SITEMAP_URL. This is used for validating the AI provided hyperlinks.

3. Add Route Handlers

The plugin makes use of Payload's server-side lexical-markdown conversion helpers.

Add the following 2 route handlers under app/(payload)/api/ folder:

/api/convert-lexical-to-markdown/route.ts

import config from '@payload-config';
import { convertLexicalToMarkdown } from '@payloadcms/richtext-lexical';
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';

export async function POST(request: Request) {
  const data: SerializedEditorState = await request.json();

  const editorConfig = ((await config).editor as any)?.editorConfig || {};
  const markdown = convertLexicalToMarkdown({
    data,
    editorConfig,
  });

  return new Response(JSON.stringify(markdown));
}

/api/convert-markdown-to-lexical/route.ts

import config from '@payload-config';
import { convertMarkdownToLexical } from '@payloadcms/richtext-lexical';

export async function POST(request: Request) {
  const markdown = (await request.json()) as string;
  const editorConfig = ((await config).editor as any)?.editorConfig || {};
  const lexicalJSON = convertMarkdownToLexical({
    editorConfig,
    markdown,
  });
  return new Response(JSON.stringify(lexicalJSON));
}

4. Add Custom Styles

Add this in your custom.scss of Payload CMS:

/* Hide Interlink Widget Playground */
#nav-global-interlink-widget-playground {
  display: none;
}
li:has(div.card-interlink-widget-playground) {
  display: none;
}

Exports

The package provides two main exports:

1. Default Export (@sparrowengg/payloadcms-plugin-interlink-content)

  • Contains the Payload CMS plugin
  • Used for registering the plugin with Payload
  • Used for interlinking individual pages

2. Component Export (@sparrowengg/payloadcms-plugin-interlink-content/interlink-bulk)

  • Contains the Payload CMS Custom View
  • Used for interlinking all the pages in a collection

Usage

Single Interlinking Instructions

1. Register the Plugin

Import and register the plugin in your Payload config:

import { buildConfig } from 'payload/config';
import { interlinkWidget } from '@sparrowengg/payloadcms-plugin-interlink-content';

export default buildConfig({
  plugins: [
    interlinkWidget({
      enabled: true,
      options: {
        serverUrl: process.env.NEXT_PUBLIC_SERVER_URL,
        sitemapUrl: process.env.NEXT_PUBLIC_SITEMAP_URL,
        pineconeTeam: process.env.NEXT_PUBLIC_PINECONE_TEAM,
        hyperlinkApi: process.env.NEXT_PUBLIC_AI_HYPERLINK_API,
      },
    }),
  ],
  // ... rest of your config
});

2. Enable the Widget

Enable the widget on necessary rich text fields:

{
  name: 'content',
  type: 'richText',
  custom: {
    interlinkWidget: true, // Add this
  },
}

3. Regenerate Payload's Importmap

pnpm payload generate:importmap

4. Using the Widget

  • Widget will appear below those rich text fields on frontend
  • Start interlinking using the button
  • If your page title field is not named 'title' and page slug field is not named 'slug', you will have to provide the current page's title and slug values through the 'Customise' option in the widget

5. Verify and Apply Changes

  • Hyperlinked content will open in a playground editor
  • Verify the changes and copy paste the whole rich text editor content to the corresponding field

⚠️ Warning: Content with images are only partially supported due to a Payload CMS bug. Hyperlinked content without images & blocks will open in a new tab in broken state. DO NOT copy paste the whole content in that case. Copy paste the hyperlinks individually from it.

Bulk Interlinking Instructions (Optional)

1. Setup Tailwind CSS for Payload CMS

Bulk Interlinking dashboard uses Tailwind CSS for styling. Create the following payload-tailwind-styles.css and import it into app/(payload)/layout.tsx:

/* Define the Tailwind layers in the correct order */
@layer theme,
base,
components,
utilities;

/* Import the Tailwind theme styles */
@import 'tailwindcss/theme.css' layer(theme);

/* Skip the preflight reset to avoid conflicts with Payload's admin styles */
/* @import 'tailwindcss/preflight.css' layer(base); */

/* Import Tailwind utilities */
@import 'tailwindcss/utilities.css' layer(utilities);

/* Support Payload's dark mode toggle by adding a custom variant */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

/* Reset styles that break Payload's admin styles */
.table {
  display: block;
}

/* Include the plugins styles in Tailwind scanning */
/* Give appropriate relative path to node modules */
@source '../../node_modules/@sparrowengg';

2. Create Custom View

Create a custom view in Payload CMS (e.g., payload/views/bulk-interlink/index.tsx):

import type { AdminViewServerProps } from 'payload';
import { DefaultTemplate } from '@payloadcms/next/templates';
import { Gutter } from '@payloadcms/ui';
import { redirect } from 'next/navigation';
import { ClientWrapper } from '@sparrowengg/payloadcms-plugin-interlink-content/interlink-bulk';

export default function BulkInterlinkDashboard({
  initPageResult,
  params,
  searchParams,
  payload,
}: AdminViewServerProps) {
  // Check if user is authenticated
  const user = initPageResult.req.user;

  if (!user) {
    redirect('/admin/login');
  }

  return (
    <DefaultTemplate
      i18n={initPageResult.req.i18n}
      locale={initPageResult.locale}
      params={params}
      payload={initPageResult.req.payload}
      permissions={initPageResult.permissions}
      searchParams={searchParams}
      user={user}
      visibleEntities={initPageResult.visibleEntities}
    >
      {/* Configure this as per your access control setup */}
      {user.roles.includes('admin') ? (
        <Gutter>
          <ClientWrapper
            collections={Object.keys(payload.collections)}
            options={{
              serverUrl: process.env.NEXT_PUBLIC_SERVER_URL,
              sitemapUrl: process.env.NEXT_PUBLIC_SITEMAP_URL,
              pineconeTeam: process.env.NEXT_PUBLIC_PINECONE_TEAM,
              hyperlinkApi: process.env.NEXT_PUBLIC_AI_HYPERLINK_API,
            }}
          />
        </Gutter>
      ) : (
        <Gutter>
          <div className="text-center">
            <h1>Insufficient Permissions</h1>
            <p>You don&apos;t have permission to access this dashboard.</p>
          </div>
        </Gutter>
      )}
    </DefaultTemplate>
  );
}

3. Link Custom View

Link the custom view to payload.config.ts:

export default buildConfig({
  admin: {
    components: {
      views: {
        bulkInterlinkView: {
          Component: '@/payload/views/BulkInterlinkDashboard',
          path: '/views/bulk-interlink',
        },
      },
    },
  },
});

4. Regenerate Payload's Importmap

pnpm payload generate:importmap

5. Access the Dashboard

Access the Bulk Interlink Dashboard at this path in browser: /admin/views/bulk-interlink

License

MIT