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

@moneybar.online/moneybar

v11.4.0

Published

The navbar of monetization. Fix the 3 money-blocking stages: forced sign-ins, silent drop-offs, and broken payment flows. Turn browsers into buyers.

Readme

MoneyBar

The navbar that gets you money faster. Complete backend infrastructure included. Setup in 1 minute.

MoneyBar handles authentication, payment processing, usage limits, and subscription management - all in one navbar component. No backend coding required.


Quick Start (2 Steps)

Step 1: Copy & Customize Config

Copy moneybar-config-template.js to your project and customize these settings:

const CONFIG = {
  // ========================================
  // CUSTOMIZABLE SETTINGS
  // ========================================

  // Security key - Get yours from Supabase
  // Run: SELECT generate_security_key('my-app', 'yourdomain.com', '[email protected]', 3)
  security_key: 'sk_...',

  // What are users doing? (shown in UI: "PDF Generations: 2/3")
  actionLabel: 'PDF Generations', // Examples: 'Image Exports', 'Downloads', 'AI Requests'

  // How many free attempts before paywall?
  freeAttemptLimit: 3,

  // Payment setup (optional - add later if needed)
  payment: [{
    provider: 'dodo',
    productId: 'pdt_xxx', // Get from Dodo Payments dashboard
    mode: 'test' // 'test' or 'live'
  }],

  // Feedback form (optional)
  feedback: {
    form: {
      title: 'Quick Feedback',
      description: 'What stopped you from upgrading?',
      option1: 'Too expensive',
      option2: 'Not enough features',
      option3: 'Need more time to evaluate'
    },
    email: [{
      provider: 'resend',
      apiKey: 'your-resend-api-key',
      fromEmail: '[email protected]'
    }]
  },

  // UI Theme (optional)
  theme: {
    name: 'emerald', // DaisyUI theme name
    primaryColor: '#059669'
  },

  // Title bar (optional)
  titleBar: {
    title: '🚀 My App',
    links: [
      { text: 'Features', url: '#features', target: '_self' },
      { text: 'Pricing', url: '#pricing', target: '_self' }
    ]
  },

  // ⚠️ DO NOT MODIFY - Required for MoneyBar to work
  supabase: {
    url: 'https://qhlzdkwcrrjjcqvupyaa.supabase.co',
    anonKey: 'eyJ...'
  }
};

Step 2: Add to HTML & Attach to Buttons

Add these scripts to your HTML:

<!DOCTYPE html>
<html>
<head>
  <title>My App</title>
</head>
<body>
  <!-- Your app content -->
  <button id="my-custom-btn">Generate PDF</button>

  <!-- Step 2a: Load MoneyBar config -->
  <script type="module" src="moneybar-config-template.js"></script>

  <!-- Step 2b: Attach to your buttons -->
  <script type="module">
    // Access the MoneyBar instance
    window.moneyBar.attachToButton('#my-custom-btn', (userContext) => {

      // Check if user is premium
      if (userContext.isPremium) {
        // ✅ Premium user - no limits!
        generatePDF(); // Your custom function

      } else {
        // Free user - show remaining attempts
        alert(`Free user. ${userContext.remaining} attempts remaining`);

        if (userContext.remaining > 0) {
          generatePDF(); // Your custom function
        }
      }
    });

    // Your custom function
    function generatePDF() {
      console.log('Generating PDF...');
      // Your PDF generation logic here
    }
  </script>
</body>
</html>

Understanding UserContext

When you attach to a button, you get a userContext object with all user info:

window.moneyBar.attachToButton('#my-btn', (userContext) => {

  // Premium status
  userContext.isPremium          // true/false - Does user have premium access?
  userContext.isAuthenticated    // true/false - Is user signed in?

  // Usage limits
  userContext.currentCount       // Number - How many times user has used the feature
  userContext.remaining          // Number - Remaining free attempts
  userContext.limit              // Number - Total free attempt limit

  // User info
  userContext.email              // String - User's email (if signed in)
  userContext.name               // String - User's name (if available)
  userContext.user               // Object - Full Supabase user object

  // Subscription details (only for subscription users)
  userContext.subscriptionType          // 'monthly', 'quarterly', 'yearly'
  userContext.subscriptionId            // String - Subscription ID
  userContext.subscriptionExpiresAt     // ISO date string - When subscription expires
  userContext.subscriptionDaysRemaining // Number - Days until expiry
  userContext.isSubscriptionActive      // true/false - Active or cancelled
  userContext.nextBillingDate           // ISO date string - Next billing date
  userContext.billingFrequency          // String - 'monthly', 'quarterly', etc.
  userContext.customerId                // String - Customer ID for portal access

});

UserContext for Different Payment Types

One-Time Payment:

{
  isPremium: true,
  subscriptionType: 'one-time',  // Indicates one-time payment
  // No subscription fields (expiresAt, nextBillingDate, etc.)
}

Subscription Payment:

{
  isPremium: true,
  subscriptionType: 'monthly',            // or 'quarterly', 'yearly'
  subscriptionId: 'sub_xxx',
  subscriptionExpiresAt: '2026-02-06',
  subscriptionDaysRemaining: 31,
  isSubscriptionActive: true,             // false if cancelled
  nextBillingDate: '2026-02-06',
  billingFrequency: 'monthly',
  customerId: 'cus_xxx'                   // For customer portal
}

Note: Payment amounts, transaction IDs, and billing details are available in your Dodo Payments dashboard.


Common Patterns

Pattern 1: Premium vs Free

window.moneyBar.attachToButton('#export-btn', (userContext) => {
  if (userContext.isPremium) {
    // Full access
    exportHighQuality();
  } else {
    // Limited access
    exportLowQuality();
  }
});

Pattern 2: Check Remaining Attempts

window.moneyBar.attachToButton('#convert-btn', (userContext) => {
  if (userContext.isPremium) {
    convert();
  } else if (userContext.remaining > 0) {
    convert();
    console.log(`${userContext.remaining} attempts left`);
  } else {
    console.log('No attempts remaining - upgrade required');
  }
});

Pattern 3: Multiple Buttons

// Attach to multiple buttons with the same logic
window.moneyBar.attachToButton('#btn1', handleAction);
window.moneyBar.attachToButton('#btn2', handleAction);
window.moneyBar.attachToButton('#btn3', handleAction);

function handleAction(userContext) {
  if (userContext.isPremium) {
    performAction();
  }
}

Pattern 4: Different Actions per Button

// Button 1: Export
window.moneyBar.attachToButton('#export-btn', (ctx) => {
  if (ctx.isPremium) exportPDF();
});

// Button 2: Convert
window.moneyBar.attachToButton('#convert-btn', (ctx) => {
  if (ctx.isPremium) convertImage();
});

Integrating Your Existing Functions

MoneyBar works with your existing functions. Here's how to integrate them properly:

Basic Integration

// Your existing function
async function generatePDF(userId, template) {
  const response = await fetch('/api/generate-pdf', {
    method: 'POST',
    body: JSON.stringify({ userId, template })
  });

  if (!response.ok) {
    throw new Error('PDF generation failed');
  }

  return await response.json();
}

// Integrate with MoneyBar
window.moneyBar.attachToButton('#generate-pdf-btn', async (userContext) => {

  if (userContext.isPremium) {
    // Premium: Unlimited access
    try {
      const result = await generatePDF(userContext.email, 'premium-template');
      alert('✅ PDF generated successfully!');
    } catch (error) {
      alert('❌ Error: ' + error.message);
      throw error; // Re-throw to prevent count increment
    }

  } else {
    // Free: Limited attempts
    try {
      const result = await generatePDF(userContext.email, 'basic-template');
      const remaining = userContext.remaining - 1;
      alert(`✅ PDF generated! ${remaining} attempts remaining.`);
    } catch (error) {
      alert(`❌ Error: ${error.message}\n\nYou still have ${userContext.remaining} attempts.`);
      throw error; // Re-throw to prevent count increment
    }
  }
});

How Success/Error Handling Works

Critical: Count increments ONLY if your function succeeds

window.moneyBar.attachToButton('#my-btn', async (userContext) => {

  try {
    // Your async function runs FIRST
    await yourCustomFunction();

    // ✅ SUCCESS PATH:
    // - Function completed without error
    // - MoneyBar increments count AFTER this
    // - User consumed 1 attempt

  } catch (error) {
    // ❌ ERROR PATH:
    // - Function threw an error
    // - Re-throw the error to prevent count increment
    // - User does NOT consume an attempt

    console.error('Function failed:', error);
    throw error; // IMPORTANT: Re-throw to prevent count increment
  }
});

Real-World Example: API Call with Error Handling

// Your existing API function
async function processImage(imageUrl, format) {
  const response = await fetch('https://api.yourapp.com/process', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ imageUrl, format })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message || 'Processing failed');
  }

  return await response.json();
}

// Integrate with MoneyBar
window.moneyBar.attachToButton('#process-image-btn', async (userContext) => {

  const imageUrl = document.getElementById('image-url').value;
  const format = userContext.isPremium ? 'high-quality' : 'standard';

  try {
    const result = await processImage(imageUrl, format);

    // Success
    if (userContext.isPremium) {
      alert('✅ Image processed in high quality!');
    } else {
      const remaining = userContext.remaining - 1;
      alert(`✅ Image processed! ${remaining} attempts remaining.`);
    }

  } catch (error) {
    // Error - API failed, network issue, etc.
    if (userContext.isPremium) {
      alert('❌ Processing failed: ' + error.message);
    } else {
      alert(`❌ Processing failed: ${error.message}\n\nNo attempt used. You still have ${userContext.remaining} attempts.`);
    }

    throw error; // Re-throw to prevent count increment
  }
});

Key Points

  1. Async Functions: MoneyBar automatically waits for async functions to complete
  2. Error Handling: If your function throws an error, count does NOT increment
  3. Re-throw Errors: Always throw error; in catch blocks to prevent count increment
  4. Success/Error Feedback: Show different messages based on outcome and user type
  5. userContext.isPremium: Use this to provide different features/quality for premium users

What MoneyBar Handles Automatically

Authentication - Google sign-in (more providers coming) ✅ Usage Tracking - Counts attempts per user per app ✅ Payment Flow - Checkout, webhooks, confirmation ✅ Subscription Management - Auto-renewal, expiry, cancellation ✅ Customer Portal - Self-service subscription management ✅ Caching - 24h cache for premium status, 60s for usage counts ✅ Multi-app Support - Same user, different apps, separate limits


Configuration Reference

| Setting | Required | Purpose | Example | |---------|----------|---------|---------| | security_key | ✅ Yes | App identifier + auth | 'sk_...' | | actionLabel | ✅ Yes | What users are doing | 'PDF Generations' | | freeAttemptLimit | ✅ Yes | Free attempts before paywall | 3 | | payment | ⬜ Optional | Payment provider config | Dodo Payments | | feedback | ⬜ Optional | Collect user feedback | Email to you | | theme | ⬜ Optional | Customize colors | DaisyUI themes | | titleBar | ⬜ Optional | Navbar title & links | Your branding | | supabase | ✅ Yes | Backend (DO NOT CHANGE) | Pre-configured |


Getting Your Security Key

MoneyBar is currently in private beta.

To get a security key for beta testing, please contact the authors:

We'll provide you with a security key and help you get set up!


Support


License

MIT