Best Free Base64 Encoder Decoder Online Tools for Developers (2025)

Base64 is one of those encoding schemes that shows up everywhere in modern development — JWT tokens, image embedding in CSS and HTML, HTTP Basic Auth headers, API credentials in environment variables, and binary data in JSON payloads. Understanding it and having reliable tools to encode and decode it is a small but genuine productivity multiplier.

This guide covers the best online Base64 encoder/decoder tools, how to do it in code (Node.js, Python, browser), and the common use cases where Base64 appears in real development work.

Quick Comparison

Tool Encode Decode URL-Safe File Support Free
base64encode.org Yes No Yes Yes Yes
base64decode.org No Yes Yes Yes Yes
base64.guru Yes Yes Yes Yes Yes
DevDocs Via console Via console Manual No Yes
Browser Console Yes Yes Partial No Yes
Node.js Buffer Yes Yes Yes Yes Yes (built-in)
Python base64 Yes Yes Yes Yes Yes (built-in)
CLI (openssl/base64) Yes Yes Varies Yes Yes

What Is Base64 and Why Does It Exist?

Base64 encodes binary data into a string of 64 printable ASCII characters (A–Z, a–z, 0–9, +, /). The reason it exists: many systems designed to handle text can’t reliably pass binary data. Email protocols, HTTP headers, and JSON were all built around ASCII or UTF-8 text, so when you need to embed binary content in those contexts, Base64 is the standard solution.

The tradeoff: Base64 increases the size of the encoded data by about 33%. A 1 KB binary file becomes roughly 1.33 KB as Base64. For most use cases — tokens, credentials, small images — this overhead is acceptable.

Standard Base64 uses: + and / as the 63rd and 64th characters, with = as padding.
URL-safe Base64 uses: - and _ instead of + and /, with optional padding. This variant avoids characters that have special meaning in URLs and HTTP.

base64encode.org and base64decode.org — The Dedicated Online Tools

These two sites are built around a single purpose each, which makes them fast to use and easy to bookmark. They’ve been reliable for years and are frequently the first result for Base64-related searches.

base64encode.org:

  • Encode text to Base64
  • Encode files to Base64 (upload a file, get Base64 output)
  • Choose between standard and URL-safe encoding
  • UTF-8, ASCII, and other character encoding options

base64decode.org:

  • Decode Base64 to text
  • Decode Base64 to binary file (for encoded images, PDFs, etc.)
  • Handles standard and URL-safe variants
  • Shows decoded output and lets you download it

Best use cases:

  • Quickly encoding credentials or tokens to paste into a config
  • Decoding a Base64-encoded value from an API response to see what’s inside
  • Encoding image files to Base64 for CSS or HTML embedding
  • Verifying that a value is valid Base64 before using it in code

These sites handle the most common online use cases. Keep both bookmarked — you’ll use them more often than you’d expect.

base64.guru — The All-in-One Option

base64.guru combines encoding and decoding in a single site with additional utilities. It’s useful when you need both operations or want more control over encoding options.

Key features:

  • Encode and decode in one interface
  • URL-safe Base64 encoding support
  • File encoding (upload a file, get Base64 string)
  • Base64 to image preview (for encoded images)
  • Encoding validation — tells you if a string is valid Base64
  • Multiple charset options

Best use cases:

  • When you’re switching between encoding and decoding frequently
  • Previewing an encoded image without writing code
  • Validating that a string is properly formed Base64

The Browser Console — The Tool You Always Have

If you’re already in a browser with devtools open, the console is a zero-overhead Base64 encoder/decoder. No extra tabs, no clipboard gymnastics.

// Encode a string to Base64
btoa('Hello, World!')
// Returns: "SGVsbG8sIFdvcmxkIQ=="

// Decode Base64 to string
atob('SGVsbG8sIFdvcmxkIQ==')
// Returns: "Hello, World!"

// URL-safe Base64 encode
btoa('Hello, World!').replace(/+/g, '-').replace(///g, '_').replace(/=/g, '')
// Returns: "SGVsbG8sIFdvcmxkIQ"

// Decode URL-safe Base64
function decodeUrlSafeBase64(str) {
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  while (str.length % 4) str += '=';
  return atob(str);
}

Important caveat: btoa() and atob() only handle ASCII/Latin-1 characters. For Unicode strings, you need a slightly different approach:

// Encode Unicode string to Base64
function encodeUnicode(str) {
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
    (match, p1) => String.fromCharCode(parseInt(p1, 16))));
}

// Decode Base64 to Unicode string
function decodeUnicode(str) {
  return decodeURIComponent(atob(str).split('').map(c =>
    '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
}

The browser console is the fastest option for quick one-off encoding when you’re already in devtools.

Node.js — The Developer-Friendly Way

Node.js has built-in Base64 support via the Buffer class, and it handles Unicode correctly without any workarounds.

// Encode string to Base64
const encoded = Buffer.from('Hello, World!').toString('base64');
// "SGVsbG8sIFdvcmxkIQ=="

// Decode Base64 to string
const decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, World!"

// URL-safe Base64 encode
const urlSafeEncoded = Buffer.from('Hello, World!').toString('base64url');
// "SGVsbG8sIFdvcmxkIQ"

// Decode URL-safe Base64
const urlSafeDecoded = Buffer.from('SGVsbG8sIFdvcmxkIQ', 'base64url').toString('utf8');
// "Hello, World!"

// Encode binary file to Base64
const fs = require('fs');
const imageData = fs.readFileSync('image.png');
const base64Image = imageData.toString('base64');

// Decode Base64 back to binary file
const buffer = Buffer.from(base64Image, 'base64');
fs.writeFileSync('decoded-image.png', buffer);

The base64url encoding option (added in Node.js 14.18.0+) handles URL-safe Base64 natively. For older Node versions, you’d manually replace + with -, / with _, and strip = padding.

Python — The base64 Standard Library Module

Python’s base64 module covers all common Base64 use cases with a clean API.

import base64

# Encode string to Base64
encoded = base64.b64encode(b'Hello, World!')
# b'SGVsbG8sIFdvcmxkIQ=='

# As a string
encoded_str = base64.b64encode(b'Hello, World!').decode('utf-8')
# 'SGVsbG8sIFdvcmxkIQ=='

# Decode Base64 to bytes
decoded = base64.b64decode('SGVsbG8sIFdvcmxkIQ==')
# b'Hello, World!'

# URL-safe Base64 (uses - and _ instead of + and /)
url_safe_encoded = base64.urlsafe_b64encode(b'Hello, World!').decode('utf-8')
# 'SGVsbG8sIFdvcmxkIQ=='  (same in this case; differs when + or / would appear)

url_safe_decoded = base64.urlsafe_b64decode('SGVsbG8sIFdvcmxkIQ==')
# b'Hello, World!'

# Encode a file
with open('image.png', 'rb') as f:
    file_data = f.read()
    encoded_file = base64.b64encode(file_data).decode('utf-8')

One important detail: Python’s b64decode accepts both padded and (with some handling) unpadded Base64. JWT libraries and API tokens often omit the trailing = padding. If you’re decoding a JWT payload manually, you may need to add padding back:

def decode_unpadded_base64(data):
    # Add padding if missing
    padding = 4 - len(data) % 4
    if padding != 4:
        data += '=' * padding
    return base64.b64decode(data)

Command-Line Tools

For shell scripting and quick terminal use, two commands handle most Base64 needs:

macOS / Linux base64

# Encode
echo -n 'Hello, World!' | base64
# SGVsbG8sIFdvcmxkIQ==

# Decode
echo 'SGVsbG8sIFdvcmxkIQ==' | base64 --decode

# Encode a file
base64 image.png > image.b64

# Decode a file
base64 --decode image.b64 > decoded-image.png

Note: macOS’s base64 and GNU base64 (on Linux) have slightly different flags. macOS uses --decode, some Linux versions use -d.

openssl

# Encode
echo -n 'Hello, World!' | openssl base64

# Decode
echo 'SGVsbG8sIFdvcmxkIQ==' | openssl base64 -d

Common Base64 Use Cases in Development

JWT Tokens

JSON Web Tokens use Base64URL encoding (without padding) for their header and payload sections. The three .-separated parts of a JWT are each Base64URL-encoded. You can decode the header and payload without a signature key — the signature is what’s validated, not the payload decoding.

# Decode JWT payload (the second section) in the terminal
echo "eyJhbGciOiJIUzI1NiJ9" | base64 --decode
# {"alg":"HS256"}

Sites like jwt.io provide a full JWT decoder with signature verification, but for quick payload inspection, the browser console or command line is faster.

HTTP Basic Authentication

Basic Auth encodes username:password as Base64 in the Authorization header:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Decoding dXNlcm5hbWU6cGFzc3dvcmQ= gives username:password. This is NOT encryption — anyone who can intercept the header can decode the credentials. Always use HTTPS with Basic Auth.

Embedding Images in CSS

/* Base64-encoded small icon embedded directly in CSS */
.icon {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...');
}

This technique eliminates an HTTP request for small images. It’s most useful for icons and small graphics under ~1-2 KB — above that, the size overhead and lack of caching make separate files preferable.

API Credentials in Environment Variables

Many APIs provide credentials as Base64-encoded strings, or require you to encode key:secret pairs for authentication. Having a reliable Base64 tool means you can verify what a value decodes to before trusting it.

Standard vs URL-Safe Base64: When Each Applies

Context Which to Use
Email attachments (MIME) Standard (+, /, = padding)
HTTP headers Standard
URL query parameters URL-safe (-, _, no padding)
JWT tokens URL-safe without padding
HTML data: URIs Standard
Filenames URL-safe (avoids /)
JSON values Standard

The key rule: if the Base64 string will appear in a URL or filename, use URL-safe encoding. Otherwise, standard Base64 is the default.

Which Should You Choose?

For quick browser-based encoding/decoding: base64encode.org and base64decode.org — purpose-built and reliable.

When you’re already in devtools: Browser console with btoa()/atob() — no extra tabs.

In Node.js code: Buffer.from(str).toString('base64') — built-in, handles Unicode, supports base64url.

In Python code: base64.b64encode() and base64.urlsafe_b64encode() — clean API, standard library.

In shell scripts: base64 command or openssl base64 — pipe-friendly and scriptable.

Base64 encoding is so fundamental that knowing how to do it in whatever environment you’re in — browser, terminal, or code — is time well spent. Keep a browser-based encoder bookmarked for quick work, and know the one-liner for your language of choice.

Find Base64 tools, encoding utilities, and more at DevPlaybook — a curated directory of tools for developers.

Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

The One Concept Behind RAG, Search, and AI Systems

If you’ve been exploring AI and stumbled across terms like RAG, vector search, or semantic similarity — there’s one concept sitting quietly underneath all of them.

Embeddings.

You’ll see this term everywhere:

  • vector databases
  • semantic search
  • similarity matching

But most explanations stop at:

“Embeddings convert text into vectors.”

That’s true.

But it doesn’t explain why they matter.
Or why everything in modern AI seems to depend on them.

🧠 What Embeddings Actually Are

At a basic level, embeddings represent text as numeric vectors — lists of numbers.

Why?

Because ML models can’t process raw text.
They need numbers.

But that’s not the interesting part.

Embeddings don’t just convert text into numbers.
They preserve meaning in those numbers
.

Each piece of text becomes a point in a high-dimensional space.
In that space:

  • similar meaning → closer together
  • different meaning → farther apart

For example:

  • “king” and “queen” → close
  • “cat” and “tiger” → close
  • “cat” and “car” → far

The numbers themselves don’t really matter.

The relationships between them do.

That’s the part that makes everything else possible.

🧩 Why We Need Them

Without embeddings, text is just… text.

There’s no clean way to:

  • compare meaning
  • measure similarity
  • search semantically.

Embeddings turn meaning into something that can be measured.
And once meaning becomes measurable, it becomes usable.

🧭 Types of Embeddings

Embeddings aren’t just for text.
Images, audio, graphs — all of them can be represented as vectors.

  • Text → words, sentences, documents
  • Image → visual features
  • Audio → sound patterns
  • Graph → relationships between entities

I didn’t realize this at first.
I thought embeddings were only a “text thing”.

But in most AI applications like search and RAG,
text embeddings are the most relevant starting point.

🔗 Word vs Sentence Embeddings

Not all text embeddings work the same way.

Word embeddings:

  • Represent individual words
  • Do not consider context
  • Same word → same vector everywhere

Think of them like isolated puzzle pieces.

So a word like “bank” gets the same embedding whether you’re talking about:

  • a riverbank
  • a savings account

Used in:

  • Named Entity Recognition (NER)
  • Part-of-Speech tagging
  • Word-level clustering

Sentence embeddings:

  • Represent full sentences or documents
  • Capture context and relationships
  • Same word → different meaning depending on usage

They look at the entire sentence and how words relate to each other.

So:

  • “I went to the bank to deposit money”
  • “I sat by the bank of the river”

…produce completely different embeddings.

Word embeddings capture meaning.
Sentence embeddings capture context.

Used in:

  • Semantic search
  • RAG (Retrieval-Augmented Generation)
  • Text similarity
  • Document classification

🌍 Where Embeddings Are Used

This is where things started making more sense to me.

Embeddings aren’t just a concept.
They show up everywhere:

  • Semantic search → find meaning, not just exact matches
  • RAG → retrieve relevant context for LLMs
  • Recommendations → find similar content
  • Memory in AI agents → store and retrieve past context
  • Text similarity & classification → measure and categorise meaning

All of these rely on one simple idea:

Find things that are close in meaning, not just exact matches.

🧮 Vector Similarity — The Engine Behind It All

Once everything becomes vectors, the question becomes:

how do you measure which ones are similar?

This is done using distance and similarity metrics.
Similarity metrics decide what “similar” actually means.

1. Cosine Similarity

  • Measures the angle between vectors
  • Ignores magnitude
  • Focuses on direction

So even if two pieces of text are very different in length,
if they point in the same direction → they’re considered similar.

That’s why it works so well for text.

Example:

A short tweet and a long article about the same topic
will point in the same direction.

Cosine similarity is the default in ~90% of modern AI systems.

Used in:

  • Semantic search
  • Document similarity
  • Recommendation systems

2. Dot Product

Dot product considers both:

  • direction
  • magnitude (vector size)

So in theory, it’s more expressive.

Used in:

  • recommendation systems (like YouTube)
  • ranking models
  • trained embedding systems

3. Euclidean Distance

  • Measures straight-line distance
  • Works fine in low dimensions.

But in high-dimensional spaces:

  • magnitude differences distort similarity
  • direction (meaning) matters more than distance

That’s why it’s less common in NLP.

Used in:

  • Clustering
  • Low-dimensional data
  • Classical ML systems

Quick Comparison

Method What it focuses on Usage
Cosine Direction Most common
Dot Product Direction + magnitude Selective
Euclidean Distance Rare

🤔 If Dot Product Is Better, Why Does Cosine Win?

This confused me for a while.

If dot product is more expressive —
and even used in recommendation systems —
then why does almost every modern application default to cosine?

Here’s what made it click:

1. Dot product only works when embeddings are learned to use magnitude

  • In some systems, embeddings are trained end-to-end
  • So magnitude becomes meaningful (e.g. preference strength)
  • Dot product can then use both direction and magnitude effectively

Systems like YouTube train their own embeddings.

In those systems:

  • magnitude = strength of preference
  • dot product becomes meaningful

But with off-the-shelf embeddings, you don’t get that.

2. In most embeddings, magnitude doesn’t mean anything

  • Most developers use pre-trained embeddings (APIs)
  • These encode meaning in direction, not length
  • So magnitude becomes unreliable

Which means: dot product ≈ cosine

Cosine is the default because most developers use pre-trained embeddings where magnitude means nothing.

Dot product is for teams who train their own models and design magnitude to mean something.

🌱 The Takeaway

At first, embeddings can feel like just a preprocessing step.
Something you do before the “real” work.

But that’s not accurate.

Embeddings are what make meaning searchable, comparable, and usable.

Without them:

  • RAG doesn’t work
  • semantic search doesn’t exist
  • recommendations break

You don’t need to memorise every model or metric.

But once embeddings make sense,
higher-level concepts become easier to place.

Detecting Earnings Manipulation with the Beneish M-Score: Python Implementation

In 1998, students at Cornell University flagged Enron as a likely earnings manipulator using a statistical model. Wall Street analysts were still recommending “buy.” The model? The Beneish M-Score. Here’s how to implement it in Python.

What is the Beneish M-Score?

Developed by Professor Messod D. Beneish at Indiana University in 1999, the M-Score is a mathematical model that uses 8 financial ratios to detect whether a company has manipulated its reported earnings.

The key threshold: -1.78

  • M-Score > -1.78: Likely manipulator
  • M-Score < -1.78: Unlikely manipulator

The 8 Variables

Each variable captures a different dimension of potential manipulation:

Variable Name What It Detects
DSRI Days Sales in Receivables Index Revenue inflation through receivables
GMI Gross Margin Index Deteriorating margins (incentive to manipulate)
AQI Asset Quality Index Capitalization of expenses
SGI Sales Growth Index High growth (more pressure to manipulate)
DEPI Depreciation Index Slower depreciation to boost earnings
SGAI SGA Expense Index Disproportionate overhead changes
TATA Total Accruals to Total Assets Earnings quality (cash vs. accruals)
LVGI Leverage Index Increasing debt pressure

Python Implementation

def beneish_m_score(
    dsri: float, gmi: float, aqi: float, sgi: float,
    depi: float, sgai: float, tata: float, lvgi: float
) -> dict:
    """
    Calculate the Beneish M-Score for earnings manipulation detection.

    Reference: Beneish, M.D. (1999). "The Detection of Earnings 
    Manipulation." Financial Analysts Journal, 55(5), 24-36.
    """
    THRESHOLD = -1.78

    m = (-4.84
         + 0.920 * dsri
         + 0.528 * gmi
         + 0.404 * aqi
         + 0.892 * sgi
         + 0.115 * depi
         - 0.172 * sgai
         + 4.679 * tata
         - 0.327 * lvgi)

    return {
        'm_score': round(m, 4),
        'likely_manipulator': m > THRESHOLD,
        'variables': {
            'DSRI': dsri, 'GMI': gmi, 'AQI': aqi, 'SGI': sgi,
            'DEPI': depi, 'SGAI': sgai, 'TATA': tata, 'LVGI': lvgi
        }
    }


def calculate_variables(current: dict, previous: dict) -> dict:
    """
    Calculate all 8 M-Score variables from financial statements.

    Args:
        current: Dict with current year financials
        previous: Dict with previous year financials

    Required keys: revenue, receivables, cogs, current_assets,
        ppe, securities, total_assets, depreciation, sga,
        current_liabilities, lt_debt, net_income, ocf
    """
    c, p = current, previous

    # DSRI: Days Sales in Receivables Index
    dsri = ((c['receivables'] / c['revenue']) / 
            (p['receivables'] / p['revenue']))

    # GMI: Gross Margin Index
    gm_prev = (p['revenue'] - p['cogs']) / p['revenue']
    gm_curr = (c['revenue'] - c['cogs']) / c['revenue']
    gmi = gm_prev / gm_curr if gm_curr != 0 else 1.0

    # AQI: Asset Quality Index
    hard_c = c['current_assets'] + c['ppe'] + c.get('securities', 0)
    hard_p = p['current_assets'] + p['ppe'] + p.get('securities', 0)
    aq_c = 1 - (hard_c / c['total_assets'])
    aq_p = 1 - (hard_p / p['total_assets'])
    aqi = aq_c / aq_p if aq_p != 0 else 1.0

    # SGI: Sales Growth Index
    sgi = c['revenue'] / p['revenue']

    # DEPI: Depreciation Index
    dep_rate_c = c['depreciation'] / (c['ppe'] + c['depreciation'])
    dep_rate_p = p['depreciation'] / (p['ppe'] + p['depreciation'])
    depi = dep_rate_p / dep_rate_c if dep_rate_c != 0 else 1.0

    # SGAI: SGA Expense Index
    sgai = ((c['sga'] / c['revenue']) / 
            (p['sga'] / p['revenue']))

    # TATA: Total Accruals to Total Assets
    tata = (c['net_income'] - c['ocf']) / c['total_assets']

    # LVGI: Leverage Index
    lev_c = (c['current_liabilities'] + c['lt_debt']) / c['total_assets']
    lev_p = (p['current_liabilities'] + p['lt_debt']) / p['total_assets']
    lvgi = lev_c / lev_p if lev_p != 0 else 1.0

    return beneish_m_score(dsri, gmi, aqi, sgi, depi, sgai, tata, lvgi)

The TATA Variable: The Most Powerful Predictor

Notice the coefficient on TATA is 4.679 – by far the largest weight in the formula. Total accruals to total assets measures how much of a company’s earnings come from actual cash versus accounting entries.

# High accruals = earnings don't match cash flow = red flag
tata = (net_income - operating_cash_flow) / total_assets

# A TATA of 0.05 adds 0.23 to the M-Score
# A TATA of 0.15 adds 0.70 to the M-Score
# This alone can push a company past the -1.78 threshold

Companies with high TATA values are generating earnings primarily through accounting entries rather than cash. Research consistently shows these earnings are less sustainable and more likely to be manipulated.

Real-World Example: The Enron Detection

In 1998, Beneish’s model flagged Enron with an M-Score well above -1.78. The key red flags:

  • High DSRI: Receivables growing faster than revenue
  • High AQI: Increasing “soft” assets (off-balance-sheet entities)
  • Extreme TATA: Massive gap between reported earnings and cash flow

The bankruptcy didn’t happen until 2001, giving the model a 3-year lead time.

Limitations

  1. Not designed for financial institutions: Banks make money differently. The sales/receivables relationship doesn’t apply the same way.

  2. False positives in high-growth companies: Rapid growth naturally elevates SGI and can push other variables higher. Not every high-growth company is manipulating.

  3. Backward-looking: The model uses last year’s data. By the time you calculate it, the manipulation may have already unwound.

  4. Probabilistic, not deterministic: An M-Score above -1.78 doesn’t prove manipulation. It says the financial patterns are consistent with companies that have historically manipulated.

Building a Quality Screen

The M-Score is most powerful when combined with complementary metrics:

def quality_triple_check(piotroski_score, altman_z, beneish_m):
    """
    Combine three academic models for comprehensive 
    financial health assessment.
    """
    checks = {
        'financial_strength': piotroski_score >= 6,  # Piotroski F-Score
        'bankruptcy_safe': altman_z > 1.81,           # Altman Z-Score  
        'earnings_clean': beneish_m < -1.78,          # Beneish M-Score
    }

    passed = sum(checks.values())

    return {
        'checks': checks,
        'passed': f"{passed}/3",
        'verdict': 'PASS' if passed == 3 else 
                   'CAUTION' if passed == 2 else 'FAIL'
    }

References

  • Beneish, M.D. (1999). “The Detection of Earnings Manipulation.” Financial Analysts Journal, 55(5), 24-36.
  • Beneish, M.D., Lee, C.M.C., & Nichols, D.C. (2013). “Earnings Manipulation and Expected Returns.” Financial Analysts Journal, 69(2), 57-82.

I’m Javier Sanz, a software engineer and value investor building tools for fundamental analysis at ValueMarkers.

Base64 Encode Decode Explained: Developer Guide with Real Use Cases

Base64 is one of the most misunderstood tools in a developer’s toolkit. It looks like encryption but provides zero security. It bloats data by 33% yet appears everywhere from JWTs to email attachments. Developers reach for it instinctively, sometimes when they should not.

This guide explains exactly what Base64 is, how it works, where you should use it, and where you absolutely should not. For quick encoding tasks, use our free Base64 Encoder/Decoder to convert text and binary data instantly.

What Is Base64?

Base64 is an encoding scheme that converts arbitrary binary data into a string of 64 printable ASCII characters. Those characters are: A-Z (26), a-z (26), 0-9 (10), + and / (2), with = used for padding — totaling 64.

The name “Base64” comes from the 64-character alphabet, the same way “Base10” describes our decimal system.

Key fact: Base64 is an encoding, not encryption. The original data can be decoded by anyone. It provides no confidentiality.

How Base64 Encoding Works

Base64 works in 3-byte chunks. Every 3 bytes of input (24 bits) become 4 Base64 characters (6 bits each):

Input:    M        a        n
Binary:   01001101 01100001 01101110
Groups:   010011 010110 000101 101110
Base64:   T      W      F      u    → "TWFu"

When input length is not divisible by 3, padding characters (=) are added:

"Ma" → 2 bytes → 3 Base64 chars + 1 padding → "TWE="
"M"  → 1 byte  → 2 Base64 chars + 2 padding → "TQ=="

Size overhead: Every 3 bytes become 4 characters, so Base64 output is always 4/3 (~33%) larger than the input.

Base64 vs Base64URL

Standard Base64 uses + and / characters, which have special meaning in URLs. Base64URL replaces these:

Standard Base64 Base64URL
+ -
/ _
= (padding) (often omitted)

JWTs use Base64URL. URLs and file names require Base64URL. When you see a token with - and _ instead of + and /, it’s Base64URL-encoded.

How to Encode and Decode in JavaScript

Browser

// Encode string to Base64
const encoded = btoa('Hello, World!');
console.log(encoded); // 'SGVsbG8sIFdvcmxkIQ=='

// Decode Base64 to string
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
console.log(decoded); // 'Hello, World!'

// Unicode strings (btoa only handles Latin-1)
const encodeUnicode = (str) =>
  btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
    (_, p1) => String.fromCharCode('0x' + p1)));

const decodeUnicode = (str) =>
  decodeURIComponent(atob(str).split('').map(
    c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));

console.log(encodeUnicode('日本語')); // '5pel5pys6Kqe'

Node.js

// Encode
const encoded = Buffer.from('Hello, World!').toString('base64');
console.log(encoded); // 'SGVsbG8sIFdvcmxkIQ=='

// Decode
const decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
console.log(decoded); // 'Hello, World!'

// Encode a file
const fs = require('fs');
const fileBuffer = fs.readFileSync('./image.png');
const base64Image = fileBuffer.toString('base64');
console.log(`data:image/png;base64,${base64Image}`);

// Base64URL (for JWTs, URLs)
const base64url = (str) => Buffer.from(str).toString('base64url');
const fromBase64url = (str) => Buffer.from(str, 'base64url').toString('utf8');

How to Encode and Decode in Python

import base64

# Encode string
text = "Hello, World!"
encoded = base64.b64encode(text.encode('utf-8'))
print(encoded)           # b'SGVsbG8sIFdvcmxkIQ=='
print(encoded.decode())  # 'SGVsbG8sIFdvcmxkIQ=='

# Decode string
decoded = base64.b64decode('SGVsbG8sIFdvcmxkIQ==').decode('utf-8')
print(decoded)  # 'Hello, World!'

# Encode binary file
with open('image.png', 'rb') as f:
    image_data = f.read()
encoded_image = base64.b64encode(image_data).decode()
data_uri = f"data:image/png;base64,{encoded_image}"

# Base64URL (for JWTs, URL-safe use)
encoded_url = base64.urlsafe_b64encode(text.encode('utf-8'))
decoded_url = base64.urlsafe_b64decode(encoded_url).decode('utf-8')

Real-World Use Cases

1. Data URIs in HTML/CSS

Embed small images directly in HTML or CSS without extra HTTP requests:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" alt="1x1 red pixel">
.icon {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==");
}

Best for: Small icons, loading spinners, critical above-the-fold images. Avoid for large images — the 33% size increase hurts more than the request savings.

2. HTTP Basic Authentication

The Authorization: Basic header encodes username:password as Base64:

const credentials = btoa('alice:supersecret123');
fetch('https://api.example.com/data', {
  headers: {
    'Authorization': `Basic ${credentials}`
  }
});

Important: Basic Auth over plain HTTP is insecure. Always use HTTPS.

3. JWT Tokens

All three JWT parts (header, payload, signature) are Base64URL-encoded:

const [header, payload] = token.split('.');
const decodedPayload = JSON.parse(atob(
  payload.replace(/-/g, '+').replace(/_/g, '/')
));

4. Email Attachments (MIME)

Email protocols (SMTP) were designed for ASCII text. Attachments are Base64-encoded in the MIME envelope:

Content-Type: application/pdf
Content-Transfer-Encoding: base64

JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIKL0ZpbHRlci9GbGF0ZURlY29k...

5. Storing Binary Data in JSON/XML

APIs that return binary data (like AI-generated images) often Base64-encode it:

import anthropic
import base64

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "describe this image",
               "images": [{"type": "base64", "media_type": "image/jpeg",
                           "data": base64.b64encode(image_bytes).decode()}]}]
)

6. Environment Variables and Config

Storing binary secrets (private keys, certificates) in environment variables:

# Encode a PEM key for use in an env var
export PRIVATE_KEY=$(base64 -w 0 private_key.pem)

# Decode it in your app
import base64, os
key = base64.b64decode(os.environ['PRIVATE_KEY'])

When NOT to Use Base64

Don’t use it for security

Base64 is trivially reversible. Using it to “hide” passwords or sensitive data gives false security:

# THIS IS NOT SECURITY
encoded_password = base64.b64encode(b"my_password")  # Anyone can decode this

Don’t use it for large files

A 10MB image becomes ~13.3MB when Base64-encoded. For large binary files, use multipart/form-data upload instead.

Don’t use it when binary transfer is already supported

REST APIs with Content-Type: application/octet-stream can send binary data directly. Base64 is only needed when the transport channel is text-only.

Quick Reference

Task JavaScript Python
Encode string btoa(str) base64.b64encode(s.encode())
Decode string atob(str) base64.b64decode(s).decode()
Encode Buffer/bytes buf.toString('base64') already bytes
Base64URL encode buf.toString('base64url') base64.urlsafe_b64encode(s)
Data URI prefix data:image/png;base64, same

Test your encoding interactively with the Base64 Encoder/Decoder — encode text, decode tokens, or convert files right in your browser.

Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Base64 Encoder Decoder: Complete Guide for Developers

Base64 Encoder Decoder: Complete Guide for Developers

Base64 encoding comes up constantly in web development: API authentication headers, JWT tokens, image data URIs, email attachments. Understanding it properly saves hours of debugging.

What Is Base64 Encoding?

Base64 converts binary data into a text-safe format using only 64 printable ASCII characters:

A-Z (26) + a-z (26) + 0-9 (10) + + and / (2) = 64 characters
= is used as padding

It’s an encoding, not encryption. Anyone can decode it. Don’t use Base64 to “hide” data.

Before encoding: Hello, World!
After encoding: SGVsbG8sIFdvcmxkIQ==

Base64 Encoder Decoder Online

DevPlaybook Base64 Tool — encode and decode Base64 instantly in your browser. No server, no account, no data stored.

Just paste your text or Base64 string and click Encode or Decode.

How to Encode/Decode Base64

JavaScript (Browser)

// Encode
btoa('Hello, World!')           // "SGVsbG8sIFdvcmxkIQ=="

// Decode
atob('SGVsbG8sIFdvcmxkIQ==')   // "Hello, World!"

Important: btoa() and atob() only handle Latin-1 characters. For Unicode:

// Encode Unicode (safe method)
function encodeBase64(str) {
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
    (_, p1) => String.fromCharCode(parseInt(p1, 16))
  ));
}

// Or use Buffer (modern browsers + Node.js)
btoa(String.fromCodePoint(...new TextEncoder().encode('Hello 🌍')));

JavaScript (Node.js)

// Encode
Buffer.from('Hello, World!').toString('base64');
// "SGVsbG8sIFdvcmxkIQ=="

// Decode
Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, World!"

// Encode a file
const fs = require('fs');
const encoded = fs.readFileSync('image.png').toString('base64');

// URL-safe variant (replaces + with - and / with _)
Buffer.from('Hello, World!').toString('base64url');
// "SGVsbG8sIFdvcmxkIQ"  (no padding)

Python

import base64

# Encode
encoded = base64.b64encode(b'Hello, World!').decode('utf-8')
# "SGVsbG8sIFdvcmxkIQ=="

# Decode
decoded = base64.b64decode('SGVsbG8sIFdvcmxkIQ==').decode('utf-8')
# "Hello, World!"

# URL-safe variant
url_safe = base64.urlsafe_b64encode(b'Hello, World!').decode('utf-8')

# Encode a file
with open('image.png', 'rb') as f:
    encoded = base64.b64encode(f.read()).decode('utf-8')

Bash / Terminal

# Encode
echo -n 'Hello, World!' | base64
# SGVsbG8sIFdvcmxkIQ==

# Decode
echo 'SGVsbG8sIFdvcmxkIQ==' | base64 --decode
# Hello, World!

# Encode a file
base64 image.png > image.b64

# Decode a file
base64 --decode image.b64 > image-restored.png

Common Use Cases

1. HTTP Basic Authentication

HTTP Basic Auth encodes username:password in Base64:

Authorization: Basic <base64(username:password)>

Example:

# Credentials: alice:s3cr3t
echo -n 'alice:s3cr3t' | base64
# YWxpY2U6czNjcjN0

curl -H 'Authorization: Basic YWxpY2U6czNjcjN0' https://api.example.com

Remember: This is just encoding, not encryption. Always use HTTPS.

2. JWT Tokens

JSON Web Tokens have three Base64URL-encoded sections separated by .:

header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decode the payload (second part):

const payload = JSON.parse(atob('eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0'));
// { sub: "1234567890", name: "Alice" }

Note: JWT uses Base64URL (no padding, + → -, / → _).

3. Image Data URIs

Embed images directly in HTML/CSS without external requests:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." />
.logo {
  background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz...');
}

Best for: small icons, inline SVGs in email templates.
Not ideal for: large images (increases HTML size by ~33%, can’t be cached separately).

4. API Request Bodies

Some APIs require Base64-encoded file uploads:

// Encode file for API upload
const file = document.querySelector('#file-input').files[0];
const reader = new FileReader();
reader.onload = (e) => {
  const base64 = e.target.result.split(',')[1]; // Remove data: prefix
  fetch('/api/upload', {
    method: 'POST',
    body: JSON.stringify({ file: base64, filename: file.name }),
  });
};
reader.readAsDataURL(file);

5. Environment Variables with Special Characters

Encoding secrets that contain special characters:

# Store a JSON config in an env var
export CONFIG=$(echo '{"api_key": "abc/123+xyz"}' | base64)

# Read it back
echo $CONFIG | base64 --decode | python3 -m json.tool

Base64 Variants

Standard Base64

Uses + and / as the 63rd and 64th characters. May need URL-encoding in URLs.

Base64URL

Replaces + with - and / with _. No padding (=). Safe for URLs and filenames.

Used by: JWT, many modern APIs, OAuth2.

// Node.js Base64URL
Buffer.from(data).toString('base64url');

// Convert standard to URL-safe
const urlSafe = standard.replace(/+/g, '-').replace(///g, '_').replace(/=/g, '');

Base64 with MIME Line Wrapping

Email standards require lines to be max 76 characters:

import base64
encoded = base64.encodebytes(data)  # Includes line breaks

Troubleshooting

Incorrect Padding Error

Base64 strings must have length divisible by 4. Pad with = if needed:

function fixPadding(base64) {
  return base64 + '='.repeat((4 - base64.length % 4) % 4);
}

Unicode/Emoji Encoding Issues

btoa() throws if the string contains characters outside Latin-1:

// This throws: "The string to be encoded contains characters outside of the Latin1 range"
btoa('Hello 🌍');

// Fix: encode as UTF-8 first
btoa(new TextEncoder().encode('Hello 🌍').reduce((s, b) => s + String.fromCharCode(b), ''));

Binary Data Corrupted After Decode

If you encode binary data (images, zips), decode back to a Buffer, not a string:

// Wrong — corrupts binary data
const decoded = atob(base64String);  // treats as text

// Right — decode to Uint8Array
const bytes = Uint8Array.from(atob(base64String), c => c.charCodeAt(0));

Performance Considerations

Base64 encoding increases data size by approximately 33% (every 3 bytes becomes 4 characters).

Original: 1000 bytes
Encoded:  ~1333 bytes

For large files, use binary transfer protocols instead:

  • HTTP multipart/form-data for file uploads
  • WebSockets with binary frames
  • Server-Sent Events with chunked encoding

Security Notes

  1. Base64 is not encryption — decoded in milliseconds by anyone
  2. Don’t store sensitive data Base64-encoded and call it secure — it isn’t
  3. JWT payload is Base64URL-encoded — visible to anyone with the token. Don’t put sensitive data in JWT payloads unless the token is also encrypted (JWE)
  4. Validate decoded data — malformed input can cause crashes; always handle decode errors

Quick Reference

Task JavaScript Python Bash
Encode string btoa(str) base64.b64encode(s.encode()).decode() `echo -n str
Decode string {% raw %}atob(str) base64.b64decode(s).decode() `echo str
Encode file {% raw %}Buffer.from(file).toString('base64') base64.b64encode(open(f,'rb').read()) base64 file
URL-safe Buffer.from(s).toString('base64url') base64.urlsafe_b64encode(s) (replace +//)

Use DevPlaybook Base64 Tool for quick browser-based encoding and decoding without writing code.

Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Screenshot API for Flask: Capture Web Pages from Your Python App in Minutes

Screenshot API for Flask: Capture Web Pages from Your Python App in Minutes

Flask developers face a familiar problem: your app needs to capture web pages as screenshots or PDFs, but you don’t want to manage Puppeteer, Selenium, or wkhtmltopdf in production.

This is exactly the problem PageBolt solves. Instead of spinning up a headless browser, managing dependencies, and debugging timeout issues, you just send a REST request. Your screenshot is ready in under a second.

The Problem: Why Not Just Use Selenium?

If you’ve tried Selenium or Puppeteer for Flask apps, you know the pain:

  • Dependency hell: Installing ChromeDriver, Chromium, or Firefox requires system packages, version management, and Docker complexity
  • Resource heavy: Each screenshot request spins up a new browser process or queues on a shared pool — not great for high-traffic apps
  • Fragile in production: Screenshots timeout, browsers crash, PDF rendering breaks on obscure CSS, and you’re debugging on someone else’s infrastructure
  • Maintenance burden: Browser versions change, Selenium releases break compatibility, CI/CD needs special flags like --no-sandbox

PageBolt removes all of this. Your code is just a few lines of requests.post().

The Solution: REST API Instead of Local Browsers

Here’s the whole idea in one example:

import requests

# Capture a website as a screenshot
response = requests.post(
    'https://api.pagebolt.dev/screenshot',
    headers={'Authorization': f'Bearer {PAGEBOLT_API_KEY}'},
    json={
        'url': 'https://example.com',
        'format': 'png'
    }
)

# response.content is your PNG file
# Save it, return it, email it, whatever
with open('screenshot.png', 'wb') as f:
    f.write(response.content)

Done. No browsers. No dependencies. No timeouts.

Complete Flask Example 1: Synchronous Screenshot Route

Let’s build a simple Flask app with a route that takes a screenshot and returns it:

from flask import Flask, request, send_file
import requests
import io
import os

app = Flask(__name__)

PAGEBOLT_API_KEY = os.getenv('PAGEBOLT_API_KEY')
PAGEBOLT_BASE_URL = 'https://api.pagebolt.dev'

@app.route('/capture-screenshot', methods=['POST'])
def capture_screenshot():
    """
    POST /capture-screenshot
    Body: { "url": "https://example.com", "format": "png" }
    Returns: PNG file
    """
    data = request.json
    url = data.get('url')

    if not url:
        return {'error': 'url is required'}, 400

    # Call PageBolt API
    response = requests.post(
        f'{PAGEBOLT_BASE_URL}/screenshot',
        headers={'Authorization': f'Bearer {PAGEBOLT_API_KEY}'},
        json={
            'url': url,
            'format': 'png',
            'width': 1280,
            'height': 720,
            'fullPage': True  # Capture full scrollable page
        }
    )

    if response.status_code != 200:
        return {'error': f'PageBolt error: {response.status_code}'}, 500

    # Return the screenshot as a file download
    return send_file(
        io.BytesIO(response.content),
        mimetype='image/png',
        as_attachment=True,
        download_name=f'screenshot.png'
    )

@app.route('/capture-pdf', methods=['POST'])
def capture_pdf():
    """
    POST /capture-pdf
    Body: { "url": "https://example.com" }
    Returns: PDF file
    """
    data = request.json
    url = data.get('url')

    if not url:
        return {'error': 'url is required'}, 400

    response = requests.post(
        f'{PAGEBOLT_BASE_URL}/pdf',
        headers={'Authorization': f'Bearer {PAGEBOLT_API_KEY}'},
        json={
            'url': url,
            'format': 'A4',
            'margin': '1cm'
        }
    )

    if response.status_code != 200:
        return {'error': f'PageBolt error: {response.status_code}'}, 500

    return send_file(
        io.BytesIO(response.content),
        mimetype='application/pdf',
        as_attachment=True,
        download_name='document.pdf'
    )

if __name__ == '__main__':
    app.run(debug=True)

Test it:

curl -X POST http://localhost:5000/capture-screenshot 
  -H 'Content-Type: application/json' 
  -d '{"url": "https://pagebolt.dev"}' 
  > screenshot.png

Done. Your Flask app now captures web pages.

Complete Flask Example 2: Asynchronous Celery Task Pattern

For higher-traffic apps, you’ll want async task processing. Here’s a Celery pattern that queues screenshot requests and stores results:

from flask import Flask, request, jsonify
from celery import Celery
import requests
import os
from datetime import datetime

app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'

celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)

PAGEBOLT_API_KEY = os.getenv('PAGEBOLT_API_KEY')
PAGEBOLT_BASE_URL = 'https://api.pagebolt.dev'

# In-memory storage for demo (use a real database in production)
screenshot_jobs = {}

@celery.task(bind=True)
def capture_screenshot_async(self, job_id, url, format='png'):
    """
    Async Celery task to capture a screenshot
    """
    try:
        response = requests.post(
            f'{PAGEBOLT_BASE_URL}/screenshot',
            headers={'Authorization': f'Bearer {PAGEBOLT_API_KEY}'},
            json={
                'url': url,
                'format': format,
                'width': 1280,
                'height': 720,
                'fullPage': True
            },
            timeout=30
        )

        if response.status_code == 200:
            # Store the screenshot in S3, database, or filesystem
            screenshot_jobs[job_id] = {
                'status': 'completed',
                'url': url,
                'timestamp': datetime.utcnow().isoformat(),
                'data': response.content  # In production, save to S3
            }
        else:
            screenshot_jobs[job_id] = {
                'status': 'failed',
                'error': f'PageBolt returned {response.status_code}'
            }
    except Exception as e:
        screenshot_jobs[job_id] = {
            'status': 'failed',
            'error': str(e)
        }

@app.route('/screenshot-async', methods=['POST'])
def screenshot_async():
    """
    Queue a screenshot capture task
    Returns: job_id for polling
    """
    data = request.json
    url = data.get('url')

    if not url:
        return {'error': 'url is required'}, 400

    job_id = f"job_{datetime.utcnow().timestamp()}"
    screenshot_jobs[job_id] = {'status': 'pending'}

    # Queue the task
    capture_screenshot_async.delay(job_id, url)

    return {
        'job_id': job_id,
        'status': 'queued',
        'poll_url': f'/screenshot-status/{job_id}'
    }

@app.route('/screenshot-status/<job_id>', methods=['GET'])
def screenshot_status(job_id):
    """
    Poll the status of a screenshot job
    """
    job = screenshot_jobs.get(job_id)

    if not job:
        return {'error': 'job not found'}, 404

    status = job.get('status')

    if status == 'pending' or status == 'in_progress':
        return {'status': status, 'job_id': job_id}
    elif status == 'completed':
        return {
            'status': 'completed',
            'job_id': job_id,
            'url': job.get('url'),
            'download_url': f'/screenshot-download/{job_id}'
        }
    else:
        return {'status': 'failed', 'error': job.get('error')}, 500

@app.route('/screenshot-download/<job_id>', methods=['GET'])
def screenshot_download(job_id):
    """
    Download the completed screenshot
    """
    job = screenshot_jobs.get(job_id)

    if not job or job.get('status') != 'completed':
        return {'error': 'screenshot not ready'}, 404

    from flask import send_file
    import io

    return send_file(
        io.BytesIO(job['data']),
        mimetype='image/png',
        as_attachment=True,
        download_name='screenshot.png'
    )

if __name__ == '__main__':
    app.run(debug=True)

Usage:

# Queue a screenshot
curl -X POST http://localhost:5000/screenshot-async 
  -H 'Content-Type: application/json' 
  -d '{"url": "https://pagebolt.dev"}'
# Returns: { "job_id": "job_1234567890", "status": "queued" }

# Poll status
curl http://localhost:5000/screenshot-status/job_1234567890

# Download when ready
curl http://localhost:5000/screenshot-download/job_1234567890 > screenshot.png

Comparison: PageBolt vs Selenium vs wkhtmltopdf

Feature PageBolt Selenium wkhtmltopdf
Setup complexity 1 API key Install ChromeDriver + Chromium Install system package
Dependency management None (just requests) Fragile versioning Outdated, unmaintained
CPU/memory per request Hosted (not your problem) Spins up new browser Heavy process
JavaScript rendering Full Full Basic
Cookie/auth support Yes (headers) Yes (Selenium webdriver) No
Timeouts Handled server-side Your responsibility Your responsibility
Cost at scale $29/mo for 10k requests $0 but hosting costs $0 but hosting costs
Maintenance burden None High (browser updates) Very high (unmaintained)

Winner for Flask: PageBolt. Zero setup, no dependency hell, predictable pricing.

Cost Analysis

PageBolt pricing:

  • Free tier: 100 requests/month
  • Paid: $29/month for 10,000 requests (~$0.003 per screenshot)

Self-hosted Selenium:

  • Infrastructure: EC2 instance ($30–$100/month depending on load)
  • Developer time: 8+ hours to set up, debug, monitor
  • Maintenance: 4+ hours/month for browser updates, timeout fixes
  • Real cost: $100+/month + your time

At just 1,000 requests/month, PageBolt pays for itself in saved infrastructure costs alone.

Real-World Example: Report Generation

Here’s a practical example — a Flask app that generates HTML reports and emails them as PDFs:

from flask import Flask, request
from celery import Celery
import requests
import smtplib
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

@celery.task
def generate_report_pdf(report_id, email, report_html_url):
    """
    Generate a PDF from an HTML report and email it
    """
    # Capture the HTML as a PDF
    response = requests.post(
        'https://api.pagebolt.dev/pdf',
        headers={'Authorization': f'Bearer {PAGEBOLT_API_KEY}'},
        json={
            'url': report_html_url,
            'format': 'A4',
            'margin': '1cm'
        }
    )

    if response.status_code != 200:
        print(f"PDF generation failed: {response.status_code}")
        return

    # Email the PDF
    msg = MIMEMultipart()
    msg['From'] = '[email protected]'
    msg['To'] = email
    msg['Subject'] = f'Report #{report_id}'

    attachment = MIMEBase('application', 'octet-stream')
    attachment.set_payload(response.content)
    attachment.add_header('Content-Disposition', 'attachment', filename=f'report_{report_id}.pdf')
    msg.attach(attachment)

    # Send via your SMTP server
    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login('[email protected]', 'your_password')
    server.send_message(msg)
    server.quit()

@app.route('/reports/<report_id>/email', methods=['POST'])
def email_report(report_id):
    email = request.json.get('email')
    report_url = f'https://yourapp.com/reports/{report_id}/view'

    generate_report_pdf.delay(report_id, email, report_url)

    return {'status': 'report queued for email'}

Next Steps

  1. Get a free API key: Visit pagebolt.dev and sign up — 100 requests/month, no credit card required
  2. Install requests: pip install requests
  3. Copy one of the examples above and run it in your Flask app
  4. Scale as needed: If you exceed 100 requests/month, upgrade to a paid plan ($29/month, cancel anytime)

Flask developers shouldn’t be managing headless browsers. With PageBolt, you get web capture in minutes, not weeks.

Try it free — 100 requests/month, no credit card. Start capturing screenshots now.

Update on the OpenAI Foundation

The OpenAI Foundation announces plans to invest at least $1 billion in curing diseases, economic opportunity, AI resilience, and community programs.