DEV Community

foxgem
foxgem

Posted on

MonaKiosk: a modern Astro integration to add paywalls to your Astro-powered content

We released MonaKiosk yesterday, a modern Astro integration for monetizing your content with Polar.sh.

With MonaKiosk, you can easily add paywalls and monetization to your Astro-powered projects. Whether you’re selling blog posts, courses, templates, or subscriptions, MonaKiosk takes care of the heavy lifting so you can focus on creating great content.

Key Features

  • Content Paywalls - Protect your premium content with automatic paywall generation
  • Flexible Pricing - Support for both one-time purchases and subscriptions
  • Simple Setup - Add a few lines to your Astro config and you're ready to go
  • Automatic Sync - Your content automatically syncs to Polar at build time
  • Built-in Auth - Email-based authentication and session management included

Installation

npm install mona-kiosk
Enter fullscreen mode Exit fullscreen mode

Quick Start

You can find the whole project in demo.

1. Setup Polar

In Polar, configure the environment values required by .env:

POLAR_ACCESS_TOKEN=polar_oat_xxxxxxxxxxxxx
POLAR_ORG_SLUG=your-org-slug
POLAR_ORG_ID=your-org-id
POLAR_SERVER=sandbox  # or 'production'
Enter fullscreen mode Exit fullscreen mode

2. Create an Astro Project

For example, run pnpm create astro@latest and choose the blog template.

Then install the required packages:

  • @astrojs/node // or other server adapters
  • mona-kiosk

The following steps use the Astro blog template as the reference.

3. Update Contents

Import PayableMetadata and merge it into the collection you want to protect.

import { PayableMetadata } from "mona-kiosk";

const blog = defineCollection({
  ...
  schema: ({ image }) =>
    z.object(...)
     .merge(PayableMetadata),
});
Enter fullscreen mode Exit fullscreen mode

Then, update the metadata in a specific content:

---
title: 'Markdown Style Guide'
description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.'
pubDate: 'Jun 19 2024'
heroImage: '../../assets/blog-placeholder-1.jpg'
price: 100 # Price in cents ($1)
---
Enter fullscreen mode Exit fullscreen mode

Note: the currency field defaults to usd. Polar currently supports USD only, so you can leave it unset for now.

4. Add Integration

In astro.config.mjs:

import { defineConfig } from "astro/config";
import { monaKiosk } from "mona-kiosk";

export default defineConfig({
  output: "server", // Required - MonaKiosk needs SSR
  integrations: [
    monaKiosk({
      polar: {
        accessToken: process.env.POLAR_ACCESS_TOKEN,
        organizationSlug: process.env.POLAR_ORG_SLUG,
        organizationId: process.env.POLAR_ORG_ID,
        server: (process.env.POLAR_SERVER as "production" | "sandbox") || "sandbox",
      },
      collections: [
        { include: "src/content/blog/**/*.md" },
      ],
    }),
  ],
  adapter: node({
    mode: "standalone",
  }),
});
Enter fullscreen mode Exit fullscreen mode

What gets injected:

  • /api/mona-kiosk/checkout - Create checkout session
  • /api/mona-kiosk/auth/signin - Email-based sign-in (For internal calling, protect it.)
  • /api/mona-kiosk/auth/signout - Sign out
  • /api/mona-kiosk/portal - Redirect to Polar customer portal
  • /mona-kiosk/signin - Default signin page (Test only, not secure for production)

🚨 SECURITY WARNING: The default /mona-kiosk/signin page is provided ONLY for sandbox testing and is NOT secure for production use.

It will only be injected when server: "sandbox" is set AND no custom signinPagePath is configured.

For production, you MUST create your own secure sign-in page or use a third-party authentication provider like BetterAuth.

5. Update Page Template

In src/pages/blog/[...slug].astro:

---
import { getEntry, render } from "astro:content";

const { slug } = Astro.params;
const post = await getEntry("blog", slug);
if (!post) return Astro.redirect("/404");

const { Content } = await render(post);
---

<BlogPost {...post.data}>
 {!Astro.locals.paywall?.hasAccess && Astro.locals.paywall?.preview ? (
    <div set:html={Astro.locals.paywall.preview} />
  ) : (
    <Content />
  )}
</BlogPost>
Enter fullscreen mode Exit fullscreen mode

Note: The middleware automatically detects payable content, checks authentication/access, generates previews, and sets Astro.locals.paywall.

6. Build and Preview

npm run dev
Enter fullscreen mode Exit fullscreen mode

Test card: 4242 4242 4242 4242 (any future date, any CVC)

Support Us

You can check out our live paywalled post: MonaKiosk + BetterAuth Integration Guide. It provides a detailed walkthrough on integrating MonaKiosk with BetterAuth and Polar — a valuable resource for advanced users.

If you’d like to support our work, consider purchasing the guide 😉.

Thank you in advance for your support!

For more information, please visit its github repo.

Top comments (0)