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

palettepro-ai

v1.6.6

Published

AI-powered makeup palette analysis: grid recognition, personalized look generation, and dashboard management

Readme

PalettePro AI

The easiest way to add interactive makeup palette grids to your e-commerce site

npm version License: MIT

PalettePro AI helps beauty brands display and manage makeup palette grids with an easy-to-integrate NPM package. Perfect for Shopify, WooCommerce, custom e-commerce sites, and beauty tech applications.

Features

Easy Integration - Add a palette widget to your product pages with just a few lines of code 🎨 Interactive Grids - Clickable shade cells with hover effects and selection 📱 Responsive Design - Works beautifully on all devices ⚡ Fast Loading - Optimized for performance 🔐 Secure - API key authentication with rate limiting 🛠️ TypeScript Support - Full type definitions included

Quick Start

1. Install the Package

npm install palettepro-ai

2. Get Your API Key

Sign up at paletteproai.com/signup or use the CLI:

npx palettepro login

3. Add to Environment Variables

For Vite projects:

VITE_PALETTEPRO_API_KEY=pp_live_your_api_key_here

For Create React App projects:

REACT_APP_PALETTEPRO_API_KEY=pp_live_your_api_key_here

4. Add to Your Product Page

import React, { useState, useEffect } from 'react';
import { GenerateLooksButton } from 'palettepro-ai';

function ProductPage({ product }) {
  const [paletteImage, setPaletteImage] = useState(null);

  // Convert product image to base64
  useEffect(() => {
    const loadImage = async () => {
      const response = await fetch(product.imageUrl);
      const blob = await response.blob();
      const reader = new FileReader();
      reader.onloadend = () => setPaletteImage(reader.result);
      reader.readAsDataURL(blob);
    };
    loadImage();
  }, [product.imageUrl]);

  if (!paletteImage) return <div>Loading...</div>;

  return (
    <div>
      <h1>{product.name}</h1>
      <img src={product.imageUrl} alt={product.name} />

      <GenerateLooksButton
        productId={product.id}
        productName={product.name}
        paletteImage={paletteImage}
        apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY}
        onLooksGenerated={(data) => {
          console.log('Generated looks:', data.looks);
        }}
      />
    </div>
  );
}

That's it! The button will:

  • ✅ Ask users for skin tone & eye color
  • ✅ Auto-detect palette grid using AI
  • ✅ Generate 5 personalized makeup looks
  • ✅ Display look cards with images

API Reference

PaletteWidget (React Component)

<PaletteWidget
  productId="product-123"        // Required: Your product identifier
  apiKey="pp_live_xxx"           // Optional: API key (uses env var by default)
  showLabels={true}              // Optional: Show shade labels (default: true)
  onCellClick={(cell, row, col) => {}}  // Optional: Click handler
  style={{}}                     // Optional: Custom styles
  className=""                   // Optional: CSS class name
/>

Props:

  • productId (string, required): Unique identifier for your product
  • apiKey (string, optional): PalettePro API key (defaults to process.env.REACT_APP_PALETTEPRO_API_KEY)
  • showLabels (boolean, optional): Display shade labels on cells (default: true)
  • onCellClick (function, optional): Callback when a cell is clicked: (cell, rowIndex, colIndex) => void
  • style (object, optional): Custom CSS styles for the widget container
  • className (string, optional): CSS class name for the widget container

GenerateLooksButton (React Component)

An AI-powered component that generates personalized makeup looks based on palette images and user preferences using Google's Gemini AI. Grid dimensions are automatically detected using AI - no need to manually specify rows and columns!

import { GenerateLooksButton } from 'palettepro-ai';

<GenerateLooksButton
  productId="urban-decay-naked-3"             // Required: Your product identifier
  productName="Urban Decay Naked 3"           // Optional: Product name for dashboard
  paletteImage={base64ImageString}            // Required: Base64-encoded image (see below)
  apiKey="pp_live_xxx"                        // Required: Your API key
  onLooksGenerated={(data) => {}}             // Optional: Success callback
  onError={(error) => {}}                     // Optional: Error handler
  style={{}}                                  // Optional: Custom button styles
  className=""                                // Optional: CSS class name
/>

Props:

  • productId (string, required): Unique identifier for your product. This is used to save grid configurations per user.
  • productName (string, optional): Display name for the product (shown in user dashboard)
  • paletteImage (string, required): Base64-encoded image string (NOT a file path - see conversion guide below)
  • apiKey (string, required): Your PalettePro API key. Can be passed as prop or set in environment variables (VITE_PALETTEPRO_API_KEY or REACT_APP_PALETTEPRO_API_KEY)
  • onLooksGenerated (function, optional): Callback when looks are successfully generated. Receives response object with looks, colors, and userPreferences.
  • onError (function, optional): Error handler callback. Receives error object.
  • style (object, optional): Custom CSS styles for the button
  • className (string, optional): CSS class name for the button

⚡ Auto-Detection:

  • Grid dimensions (rows × columns) are automatically detected using AI on first use
  • Bounding box coordinates are calculated to focus on the makeup pans
  • Configuration is saved and reused for subsequent requests (no redundant API calls)
  • Works on first use - no manual grid setup required!

Converting Images to Base64

CRITICAL: The paletteImage prop must be a base64-encoded string, not a file path. Passing a file path will result in a 500 error.

Here are helper functions to convert images to base64:

// Method 1: Convert image URL to base64
const imageUrlToBase64 = async (imageUrl) => {
  const response = await fetch(imageUrl);
  const blob = await response.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
};

// Method 2: Convert File object to base64 (e.g., from file input)
const fileToBase64 = (file) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
};

// Method 3: Convert img element to base64
const imgElementToBase64 = (imgElement) => {
  const canvas = document.createElement('canvas');
  canvas.width = imgElement.naturalWidth;
  canvas.height = imgElement.naturalHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(imgElement, 0, 0);
  return canvas.toDataURL('image/png');
};

Complete Usage Example

import React, { useState, useEffect } from 'react';
import { GenerateLooksButton } from 'palettepro-ai';

function ProductPage({ product }) {
  const [base64Image, setBase64Image] = useState(null);
  const [generatedLooks, setGeneratedLooks] = useState(null);

  useEffect(() => {
    // Convert your image to base64 before passing to component
    const loadImage = async () => {
      try {
        const base64 = await imageUrlToBase64(product.image);
        setBase64Image(base64);
      } catch (error) {
        console.error('Failed to load image:', error);
      }
    };

    loadImage();
  }, [product.image]);

  if (!base64Image) {
    return <div>Loading palette...</div>;
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <img src={product.image} alt={product.name} />

      <GenerateLooksButton
        productId={product.id}
        productName={product.name}
        paletteImage={base64Image}  // ✅ Correct: base64 string
        apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY}  // ✅ Required: API key
        onLooksGenerated={(data) => {
          console.log('Generated looks:', data.looks);
          console.log('Palette colors:', data.colors);
          console.log('User preferences:', data.userPreferences);
          setGeneratedLooks(data.looks);
        }}
        onError={(error) => {
          console.error('Error generating looks:', error);
          alert('Failed to generate looks. Please try again.');
        }}
      />

      {generatedLooks && (
        <div className="looks-display">
          <h2>Your Personalized Looks</h2>
          {generatedLooks.map((look, index) => (
            <div key={index} className="look-card">
              {look.generatedImageUrl && (
                <img src={look.generatedImageUrl} alt={look.title} />
              )}
              <h3>{look.title}</h3>
              <p>{look.description}</p>
              <h4>Shades Used:</h4>
              <p>Colors: {look.colorNumbers.join(', ')}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// Helper function (include this in your utils)
const imageUrlToBase64 = async (imageUrl) => {
  const response = await fetch(imageUrl);
  const blob = await response.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
};

Key Points:

  1. Convert image to base64 before passing to component
  2. Pass apiKey prop (or set in environment variables)
  3. Use actual product data (product.id, product.name) - NOT hardcoded strings
  4. Don't pass rows/cols - they're auto-detected
  5. Handle loading state while converting image

Common Errors

WRONG: Missing apiKey

<GenerateLooksButton
  productId={product.id}
  productName={product.name}
  paletteImage={base64Image}
  // ❌ Missing apiKey! This will fail with 400 error
/>

WRONG: Passing a file path instead of base64

<GenerateLooksButton
  productId={product.id}
  paletteImage="/images/palette.jpg"  // ❌ This will fail with 400 error!
  apiKey={apiKey}
/>

WRONG: Using placeholder values

<GenerateLooksButton
  productId="your-product-id"  // ❌ Hardcoded placeholder
  productName="Your Product Name"  // ❌ Hardcoded placeholder
  paletteImage={base64Image}
  apiKey={apiKey}
/>

WRONG: Passing rows and cols (legacy)

<GenerateLooksButton
  productId={product.id}
  paletteImage={base64Image}
  rows={4}  // ❌ Not needed - auto-detected
  cols={6}  // ❌ Not needed - auto-detected
  apiKey={apiKey}
/>

CORRECT: Complete setup

const base64 = await imageUrlToBase64(product.image);
<GenerateLooksButton
  productId={product.id}  // ✅ Use actual product ID
  productName={product.name}  // ✅ Use actual product name
  paletteImage={base64}  // ✅ Base64 string (format: data:image/png;base64,...)
  apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY}  // ✅ API key from env
/>

PaletteProClient (Node.js)

const { PaletteProClient } = require('palettepro-ai');

const client = new PaletteProClient(apiKey); // apiKey optional if in env

Methods:

getGridConfig(productId)

Get palette grid configuration for a product.

const config = await client.getGridConfig('product-123');
// Returns: { rows, cols, cells, croppedImage, boundingBox, timestamp }

Returns null if no configuration exists for the product.

saveGridConfig(productId, config) (Admin only)

Save palette grid configuration for a product. Requires admin API key.

await client.saveGridConfig('product-123', {
  rows: 4,
  cols: 6,
  cells: [[/* cell data */]],
  croppedImage: 'data:image/png;base64,...',
  boundingBox: { x, y, width, height },
  timestamp: Date.now()
});

deleteGridConfig(productId) (Admin only)

Delete palette grid configuration. Requires admin API key.

await client.deleteGridConfig('product-123');

ping()

Check API connection and health.

const isOnline = await client.ping();
console.log('API is online:', isOnline);

CLI Commands

npx palettepro login

Authenticate and get your API key. Interactive command that:

  1. Sends a verification code to your email
  2. Verifies the code
  3. Generates or retrieves your API key
  4. Optionally saves it to your .env file

npx palettepro help

Display help information and available commands.

Environment Variables

# Required: Your PalettePro API key
PALETTEPRO_API_KEY=pp_live_your_api_key_here

# Optional: Custom API URL (defaults to https://www.paletteproai.com)
PALETTEPRO_API_URL=https://www.paletteproai.com

# For Create React App, use REACT_APP_ prefix:
REACT_APP_PALETTEPRO_API_KEY=pp_live_your_api_key_here
REACT_APP_PALETTEPRO_API_URL=https://www.paletteproai.com

# For Vite apps, use VITE_ prefix:
VITE_PALETTEPRO_API_KEY=pp_live_your_api_key_here
VITE_PALETTEPRO_API_URL=https://www.paletteproai.com

Note: The GenerateLooksButton component automatically detects whether you're using Vite or Create React App and loads the appropriate environment variables.

Examples

Basic React Integration

import React from 'react';
import { PaletteWidget } from 'palettepro-ai';

function ProductPage({ product }) {
  return (
    <div className="product-page">
      <h1>{product.name}</h1>
      <img src={product.image} alt={product.name} />

      <PaletteWidget
        productId={product.id}
        onCellClick={(cell) => {
          // Add shade to cart, show details, etc.
          console.log('Selected:', cell.label, cell.color);
        }}
      />
    </div>
  );
}

Custom Styled Widget

<PaletteWidget
  productId="product-123"
  style={{
    maxWidth: '800px',
    margin: '2rem auto',
    padding: '1rem',
    backgroundColor: '#f9f9f9',
    borderRadius: '12px'
  }}
  className="my-custom-palette"
/>

Node.js Backend Example

const express = require('express');
const { PaletteProClient } = require('palettepro-ai');

const app = express();
const paletteClient = new PaletteProClient();

app.get('/api/products/:id/palette', async (req, res) => {
  try {
    const config = await paletteClient.getGridConfig(req.params.id);

    if (!config) {
      return res.status(404).json({ error: 'Palette not found' });
    }

    res.json(config);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);

Next.js Integration

// pages/products/[id].js
import { PaletteWidget } from 'palettepro-ai';

export default function Product({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>

      <PaletteWidget
        productId={product.id}
        onCellClick={(cell) => {
          // Track analytics
          window.gtag('event', 'shade_selected', {
            product_id: product.id,
            shade: cell.label
          });
        }}
      />
    </div>
  );
}

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>My Product</title>
</head>
<body>
  <div id="palette-container"></div>

  <script type="module">
    import { PaletteWidget } from 'https://cdn.jsdelivr.net/npm/palettepro-ai/dist/palettepro.esm.js';

    // Render widget
    const container = document.getElementById('palette-container');
    ReactDOM.render(
      <PaletteWidget productId="product-123" />,
      container
    );
  </script>
</body>
</html>

Platform-Specific Guides

Shopify

  1. Install the package in your theme:
npm install palettepro-ai
  1. Add to your product template:
{% if product.metafields.palettepro.enabled %}
  <div id="palette-widget"></div>

  <script type="module">
    import { PaletteWidget } from 'palettepro-ai';

    ReactDOM.render(
      <PaletteWidget productId="{{ product.id }}" />,
      document.getElementById('palette-widget')
    );
  </script>
{% endif %}

WooCommerce

Add to your functions.php:

function add_palettepro_widget() {
    if (is_product()) {
        global $product;
        ?>
        <div id="palettepro-widget"></div>
        <script type="module">
          import { PaletteWidget } from 'palettepro-ai';
          ReactDOM.render(
            React.createElement(PaletteWidget, {
              productId: '<?php echo $product->get_id(); ?>'
            }),
            document.getElementById('palettepro-widget')
          );
        </script>
        <?php
    }
}
add_action('woocommerce_after_single_product_summary', 'add_palettepro_widget', 20);

Pricing

Visit paletteproai.com/pricing for current pricing plans:

  • Starter: 1,000 requests/month, 1 domain - $49/mo
  • Professional: 5,000 requests/month, 3 domains - $149/mo
  • Enterprise: Unlimited requests, unlimited domains - $499/mo

All plans include:

  • API access
  • CLI tools
  • React components
  • Technical support
  • Regular updates

Support

Error Handling

import { PaletteWidget } from 'palettepro-ai';

function ProductPage() {
  const handleError = (error) => {
    console.error('Palette error:', error);
    // Show fallback UI or error message
  };

  return (
    <PaletteWidget
      productId="product-123"
      onError={handleError}
    />
  );
}

For backend errors:

const client = new PaletteProClient();

try {
  const config = await client.getGridConfig('product-123');
} catch (error) {
  if (error.status === 404) {
    console.log('No palette found');
  } else if (error.status === 429) {
    console.log('Rate limited');
  } else {
    console.error('API error:', error.message);
  }
}

Rate Limiting

All API requests are rate-limited to 100 requests per 15 minutes per IP address. If you exceed this limit, you'll receive a 429 Too Many Requests response.

Best practices:

  • Cache palette configurations on your server
  • Implement exponential backoff for retries
  • Monitor your usage in the dashboard

Troubleshooting

GenerateLooksButton Errors

500 Error: "Base64 decoding failed"

Error message: Base64 decoding failed for "/images/palette.jpg"

Cause: You're passing a file path instead of base64-encoded image data.

Solution: Convert the image to base64 before passing it to the component:

const base64Image = await imageUrlToBase64('/images/palette.jpg');
<GenerateLooksButton paletteImage={base64Image} {...otherProps} />

401 Error: "Invalid API key"

Cause: API key is missing or incorrect.

Solution:

  1. Check that your API key is properly set in .env:
    • For Vite: VITE_PALETTEPRO_API_KEY=pp_live_...
    • For Create React App: REACT_APP_PALETTEPRO_API_KEY=pp_live_...
  2. Restart your dev server after changing .env
  3. Verify your API key in the dashboard

307 CORS/Redirect Errors

Cause: Incorrect API URL in environment variables.

Solution: Use https://www.paletteproai.com (with www):

VITE_PALETTEPRO_API_URL=https://www.paletteproai.com

Wrong: https://paletteproai.com (without www) ❌ Wrong: https://api.paletteproai.comCorrect: https://www.paletteproai.com

TypeScript

Full TypeScript definitions are included:

import { PaletteProClient, PaletteWidget } from 'palettepro-ai';

interface GridConfig {
  rows: number;
  cols: number;
  cells: Cell[][];
  croppedImage: string;
  boundingBox: BoundingBox;
  timestamp: number;
}

const client = new PaletteProClient();
const config: GridConfig = await client.getGridConfig('product-123');

License

MIT © Solasyn Labs LLC

Links


Made with ❤️ by Solasyn Labs