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

remark-affiliate-card

v1.0.0

Published

remark plugin that renders Amazon / Rakuten / Yahoo! affiliate product cards

Readme

remark-affiliate-card

npm CI downloads license

A remark plugin that renders Amazon product cards from a ::amazon directive, with optional Rakuten and Yahoo! Shopping links alongside.

この記事で使ったウマです。

::amazon{asin="B003F5VYKW"}

Product data comes from the Amazon Creators API, is committed to your repository as JSON, and is refreshed with a CLI — so builds and deploys never depend on the API being up.

Install

bun add remark-affiliate-card remark-directive

Any unified pipeline can use it:

import remarkDirective from 'remark-directive'
import remarkParse from 'remark-parse'
import { createRemarkAmazon } from 'remark-affiliate-card'
import { unified } from 'unified'

unified()
  .use(remarkParse)
  .use(remarkDirective)
  .use(...createRemarkAmazon(options))

The default export is the raw remark plugin, but it expects already-resolved product data and credentials — nothing a consumer should assemble by hand. createRemarkAmazon(options) does that resolution and hands back a [plugin, resolvedOptions] pair, which is why it is spread into .use() here rather than passed as the plugin itself.

remark-directive is yours to register: nothing parses ::amazon on its own, and without it the directive reaches the page as literal text. Sites that only convert bare Amazon URLs (bareUrls: true) do not need it.

With Astro

Astro 7's native Markdown processor runs no remark or rehype plugins, so a site whose Markdown depends on them declares the unified processor itself. This is the shape every site using this plugin is on:

// astro.config.mjs
import { unified } from '@astrojs/markdown-remark'
import { createRemarkAmazon } from 'remark-affiliate-card'

markdown: {
  processor: unified({
    remarkPlugins: [remarkDirective, createRemarkAmazon(), /* … */],
  }),
}

createRemarkAmazon() reads the product data and the credentials for you and returns the [plugin, options] pair. Import the stylesheet yourself:

@import "remark-affiliate-card/card.css";

Configure

createRemarkAmazon({
  dataFile: 'src/data/amazon-products.json',
  labels: {
    kicker: 'PR',          // overrides the brand; see Disclosure below
    cta: 'Amazonで見る',
    shopsLabel: '他で探す',
    showAsin: false,
    showDescription: false,
    frameClass: '',        // extra class on the card root, for your own skin
    linkClass: '',         // extra class on every anchor; see Styling below
  },
  bareUrls: false,         // also convert standalone Amazon URLs
})

Credentials come from .env or the environment:

| Variable | Required | Purpose | | --- | --- | --- | | AMAZON_AFFILIATE_TAG | yes | Associates tag on Amazon links | | AMAZON_CREATOR_CREDENTIAL_ID | for fetch | Creators API credential | | AMAZON_CREATOR_SECRET | for fetch | Creators API credential | | AMAZON_CREATOR_CREDENTIAL_VERSION | no | defaults to 3.3 | | AMAZON_MARKETPLACE | no | defaults to www.amazon.co.jp | | RAKUTEN_AFFILIATE_ID | no | omit to hide the Rakuten button | | YAHOO_VC_SID | no | ValueCommerce site id | | YAHOO_VC_PID | no | ValueCommerce program id |

A shop with no credentials gets no button, rather than a link that goes nowhere.

Fetching product data

bunx affiliate-card fetch

Scans your posts for ASINs, asks the Creators API about the ones that are new or older than 90 days, and writes src/data/amazon-products.json. Commit that file: it is both the build's data source and its own cache.

--posts <dir>   posts to scan  (default src/content/posts)
--out <file>    product file   (default src/data/amazon-products.json)
--force         refetch everything, ignoring cache freshness

An ASIN the API stops resolving keeps whatever was captured when it still existed. Products get delisted and never come back, and overwriting those records with empty stubs would blank out cards that render perfectly well today. A brand-new ASIN that cannot be resolved falls back to a plain affiliate link.

The command exits non-zero only when every batch fails, which means a credential or connectivity problem. Individual ASINs going missing is routine and does not fail a build.

Note on the API

This talks to the Creators API, not Product Advertising API v5 — Amazon retired PA-API on 2026-05-15, and its access keys do not work here. Issue a credential pair in Associates Central → Tools → Creators API.

The API does not return customer reviews: customerReviews comes back null, so the card renders no star rating. It does return the brand, which the card shows above the title.

What the card shows

Image, brand, title, price, and a buy button — plus Rakuten and Yahoo! search links when those are configured.

Deliberately absent by default:

  • The ASIN. It identifies the product to Amazon, not to a reader.
  • The description. Amazon's description is the same keyword-stuffed marketing copy as the title, so showing both doubles the noise.
  • A full-length title. Amazon titles run past 100 characters of search terms; the card clamps to two lines so the price and the buy button stay above the fold.

Each is available through labels for a site that wants it.

Disclosure

Japan's ステマ規制 requires advertising to be identifiable as advertising, and affiliate placements fall under it. The card carries no disclosure by default, because a site can equally disclose once per article — repeating it on every card is the noisier of the two options.

If you want it on the card, set labels: { kicker: 'PR' }. If you disclose at the article level instead, make sure something on the page actually says so.

Styling

The stylesheet drives every colour, radius and font through an --aff-* custom property, so you re-skin by overriding variables rather than forking markup:

.amazon-card {
  --aff-accent: #d2691e;
  --aff-radius: 0;
}

Dark mode follows your page's own marker — .dark or [data-theme="dark"] on the root element. It deliberately does not use prefers-color-scheme: sites that resolve an "auto" setting in JavaScript stamp the result on <html>, and a media query would fight that and darken the card for a reader who had explicitly chosen light.

If your site styles every <a> in prose, its rules will outrank this package's. Rather than escalating specificity, pass your own escape-hatch class:

// for a site with `.custom-md a:not(.no-styling) { … }`
createRemarkAmazon({ labels: { linkClass: 'no-styling' } })

Bare Amazon URLs

Off by default. With bareUrls: true, a paragraph containing nothing but an Amazon link becomes a card. A link inside a sentence stays a link.

It is opt-in because enabling it rewrites existing prose — that is a content change, and it should be a decision rather than a surprise after an upgrade.

Using the pieces directly

Every layer is exported, so you can render a card outside the remark pipeline:

import { renderAmazonCard } from 'remark-affiliate-card/card'
import { resolveShopLinks } from 'remark-affiliate-card/shops'

const links = resolveShopLinks('メダリスト ジェル', { rakutenAffiliateId: '…' })
const html = renderAmazonCard({ url, title, price }, links)

renderAmazonCard and the URL builders are pure: credentials and text arrive as arguments, never from the environment.

License

MIT