Skip to content

Brand Style Guide Generator Cookbook

Updated Feb 2026

Build a Node.js application that automatically extracts brand identity from any website and generates a professional PDF style guide. Uses Firecrawl's branding format to pull colors, typography, spacing, logos, and theme data.

What Gets Extracted

Firecrawl's branding format returns a comprehensive design system profile:

CategoryFields
ColorsPrimary, secondary, accent, background, and text colors (hex values)
TypographyFont families (primary, heading, code), size scale (h1-h6, body), font weights
SpacingBase unit measurements, border radius specifications
ImagesLogo URL, favicon URL, OG image URL
ThemeColor scheme designation (light or dark mode)

Setup

Initialize the Project

bash
mkdir brand-style-generator
cd brand-style-generator
npm init -y
npm i @mendable/firecrawl-js pdfkit
npm i -D typescript tsx @types/node @types/pdfkit

Environment Variable

env
FIRECRAWL_API_KEY=fc-your-api-key

TypeScript Config

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

Step 1: Extract Brand Data

The core extraction uses Firecrawl's branding format -- a single API call returns the full brand profile.

typescript
// src/extract.ts
import FirecrawlApp from "@mendable/firecrawl-js";

const firecrawl = new FirecrawlApp({
  apiKey: process.env.FIRECRAWL_API_KEY!,
});

export interface BrandProfile {
  colors: {
    primary: string;
    secondary: string;
    accent: string;
    background: string;
    text: string;
  };
  typography: {
    primaryFont: string;
    headingFont: string;
    codeFont: string;
    sizes: {
      h1: string;
      h2: string;
      h3: string;
      h4: string;
      h5: string;
      h6: string;
      body: string;
    };
    weights: Record<string, string>;
  };
  spacing: {
    baseUnit: string;
    borderRadius: string;
  };
  images: {
    logo: string | null;
    favicon: string | null;
    ogImage: string | null;
  };
  theme: {
    colorScheme: "light" | "dark";
  };
}

export async function extractBrand(url: string): Promise<BrandProfile> {
  const result = await firecrawl.scrapeUrl(url, {
    formats: ["branding"],
  });

  return result.branding as BrandProfile;
}

Usage:

typescript
const brand = await extractBrand("https://stripe.com");
console.log(brand.colors.primary);     // "#635BFF"
console.log(brand.typography.primaryFont); // "Inter"
console.log(brand.theme.colorScheme);  // "light"

Step 2: Generate the PDF

Use PDFKit to create a styled A4 document with visual swatches and typography samples.

typescript
// src/generate-pdf.ts
import PDFDocument from "pdfkit";
import fs from "fs";
import { BrandProfile } from "./extract";

export function generateStyleGuide(
  brand: BrandProfile,
  outputPath: string,
  websiteUrl: string
) {
  const doc = new PDFDocument({ size: "A4", margin: 50 });
  const stream = fs.createWriteStream(outputPath);
  doc.pipe(stream);

  // --- Header ---
  doc
    .rect(0, 0, doc.page.width, 100)
    .fill(brand.colors.primary);

  doc
    .fontSize(28)
    .fillColor("#FFFFFF")
    .text("Brand Style Guide", 50, 35);

  doc
    .fontSize(12)
    .fillColor("#FFFFFF")
    .text(websiteUrl, 50, 70);

  doc.moveDown(4);

  // --- Colors Section ---
  doc
    .fontSize(20)
    .fillColor(brand.colors.text)
    .text("Colors", 50);

  doc.moveDown(0.5);

  const colorEntries = [
    { name: "Primary", hex: brand.colors.primary },
    { name: "Secondary", hex: brand.colors.secondary },
    { name: "Accent", hex: brand.colors.accent },
    { name: "Background", hex: brand.colors.background },
    { name: "Text", hex: brand.colors.text },
  ];

  let xPos = 50;
  for (const color of colorEntries) {
    // Color swatch
    doc
      .rect(xPos, doc.y, 80, 50)
      .fill(color.hex);

    doc
      .fontSize(10)
      .fillColor(brand.colors.text)
      .text(color.name, xPos, doc.y + 55, { width: 80, align: "center" })
      .text(color.hex, xPos, doc.y + 5, { width: 80, align: "center" });

    xPos += 100;
  }

  doc.moveDown(6);

  // --- Typography Section ---
  doc
    .fontSize(20)
    .fillColor(brand.colors.text)
    .text("Typography", 50);

  doc.moveDown(0.5);

  doc
    .fontSize(12)
    .text(`Primary Font: ${brand.typography.primaryFont}`)
    .text(`Heading Font: ${brand.typography.headingFont}`)
    .text(`Code Font: ${brand.typography.codeFont}`);

  doc.moveDown(1);

  // Size scale
  const sizeEntries = Object.entries(brand.typography.sizes);
  for (const [level, size] of sizeEntries) {
    doc
      .fontSize(10)
      .fillColor(brand.colors.text)
      .text(`${level.toUpperCase()}: ${size}`, 50);
  }

  doc.moveDown(2);

  // --- Spacing Section ---
  doc
    .fontSize(20)
    .fillColor(brand.colors.text)
    .text("Spacing & Layout", 50);

  doc.moveDown(0.5);

  doc
    .fontSize(12)
    .text(`Base Unit: ${brand.spacing.baseUnit}`)
    .text(`Border Radius: ${brand.spacing.borderRadius}`);

  doc.moveDown(2);

  // --- Theme Section ---
  doc
    .fontSize(20)
    .fillColor(brand.colors.text)
    .text("Theme", 50);

  doc.moveDown(0.5);

  doc
    .fontSize(12)
    .text(`Color Scheme: ${brand.theme.colorScheme}`);

  // --- Images Section ---
  if (brand.images.logo || brand.images.favicon) {
    doc.moveDown(2);
    doc
      .fontSize(20)
      .fillColor(brand.colors.text)
      .text("Brand Assets", 50);

    doc.moveDown(0.5);

    if (brand.images.logo) {
      doc.fontSize(12).text(`Logo: ${brand.images.logo}`);
    }
    if (brand.images.favicon) {
      doc.fontSize(12).text(`Favicon: ${brand.images.favicon}`);
    }
    if (brand.images.ogImage) {
      doc.fontSize(12).text(`OG Image: ${brand.images.ogImage}`);
    }
  }

  doc.end();

  return new Promise<void>((resolve) => {
    stream.on("finish", resolve);
  });
}

Step 3: Main Script

Tie the extraction and PDF generation together.

typescript
// src/index.ts
import { extractBrand } from "./extract";
import { generateStyleGuide } from "./generate-pdf";

async function main() {
  const url = process.argv[2] || "https://stripe.com";
  const output = process.argv[3] || "style-guide.pdf";

  console.log(`Extracting brand data from ${url}...`);
  const brand = await extractBrand(url);

  console.log("Brand data extracted:");
  console.log(`  Primary color: ${brand.colors.primary}`);
  console.log(`  Primary font: ${brand.typography.primaryFont}`);
  console.log(`  Theme: ${brand.theme.colorScheme}`);

  console.log(`\nGenerating PDF: ${output}...`);
  await generateStyleGuide(brand, output, url);

  console.log("Done! Style guide generated.");
}

main().catch(console.error);

Run It

bash
npx tsx src/index.ts https://stripe.com stripe-style-guide.pdf
npx tsx src/index.ts https://vercel.com vercel-style-guide.pdf
npx tsx src/index.ts https://linear.app linear-style-guide.pdf

Batch Processing Multiple Websites

Generate style guides for multiple sites in a single run.

typescript
// src/batch.ts
import { extractBrand } from "./extract";
import { generateStyleGuide } from "./generate-pdf";

const websites = [
  "https://stripe.com",
  "https://vercel.com",
  "https://linear.app",
  "https://figma.com",
  "https://notion.so",
];

async function batchGenerate() {
  for (const url of websites) {
    const domain = new URL(url).hostname.replace("www.", "");
    const outputFile = `${domain}-style-guide.pdf`;

    console.log(`Processing ${domain}...`);

    try {
      const brand = await extractBrand(url);
      await generateStyleGuide(brand, outputFile, url);
      console.log(`  Saved: ${outputFile}`);
    } catch (error) {
      console.error(`  Failed: ${error}`);
    }
  }
}

batchGenerate();

Extension Ideas

ExtensionDescription
JSON exportSave brand data as JSON for design system tooling
Component style docsGenerate CSS variables and utility classes from the brand profile
Comparison reportExtract two sites and generate a side-by-side comparison
Custom PDF themesStyle the PDF itself using the extracted brand colors
Image downloadsFetch and embed logos/favicons directly in the PDF
Web dashboardBuild a Next.js frontend to input URLs and preview results

Python Alternative

python
from firecrawl import FirecrawlApp

app = FirecrawlApp(api_key="fc-YOUR_API_KEY")

result = app.scrape_url(
    "https://stripe.com",
    params={"formats": ["branding"]},
)

brand = result["branding"]
print(f"Primary color: {brand['colors']['primary']}")
print(f"Primary font: {brand['typography']['primaryFont']}")
print(f"Theme: {brand['theme']['colorScheme']}")

TIP

The branding format works best on marketing pages and homepages where CSS custom properties and design tokens are most visible. For more accurate results, target the main homepage rather than documentation or blog pages.

Resources