ScreenshotNeo

BlogHow-to

Bar Graph Image API: Generate PNG, SVG, and WebP Charts

Generate bar graph images with QuickChart or Image-Charts, choose formats and options, troubleshoot failures, and automate production rendering.

By the ScreenshotNeo team29 September 20268 min read

Bar Graph Image API: Generate PNG, SVG, and WebP Charts

A bar graph image API accepts chart data and rendering options over HTTP, then returns a raster or vector image that you can embed in an email, report, dashboard, social card, PDF, or generated web page. For a current project, start with QuickChart when you want a Chart.js configuration, plugins, detailed styling, or several output formats. Choose Image-Charts when a compact parameterized URL and predefined grouped or stacked variants are more convenient.

The basic workflow is:

  1. Prepare category labels and numeric series.
  2. Choose a chart service and request method.
  3. Send chart type, data, dimensions, and styling.
  4. Save the response as PNG, SVG, WebP, JPG, PDF, or base64, depending on the service and endpoint.
  5. Validate the image, cache it where appropriate, and expose failures to your application.

What a bar graph image API returns

Unlike a browser charting library, an image API performs rendering on a remote service. Your application does not need a DOM, canvas implementation, or headless browser. The response body is an image, and the HTTP status and headers indicate whether the request succeeded.

A bar graph image API turns chart data and rendering options into an embeddable image.
A bar graph image API turns chart data and rendering options into an embeddable image.
Decision What to choose Why it matters
Request model Chart.js object or query parameters Objects are expressive; URLs are easy to cache and place in an <img> tag.
Transport GET, POST, or both GET is convenient for short charts. POST avoids URL-length limits and keeps large configurations out of logs.
Output PNG, JPG, WebP, SVG, PDF, or base64 Raster formats work broadly; SVG stays sharp at any size; PDF is useful for print workflows.
Layout Vertical, horizontal, grouped, or stacked Orientation and stacking change how viewers compare categories and series.

QuickChart: render a bar graph with a Chart.js configuration

QuickChart renders Chart.js configurations through its /chart endpoint. A configuration contains type, data, and optional options. Its documented POST interface can return PNG, JPG, WebP, SVG, PDF, or base64 output.

Minimal cURL request

curl -X POST https://quickchart.io/chart \
  -H 'Content-Type: application/json' \
  -d '{
    "width": 900,
    "height": 500,
    "format": "png",
    "chart": {
      "type": "bar",
      "data": {
        "labels": ["Q1", "Q2", "Q3", "Q4"],
        "datasets": [{
          "label": "Revenue",
          "data": [120, 180, 150, 230],
          "backgroundColor": "#4f46e5"
        }]
      },
      "options": {
        "plugins": {"legend": {"display": false}},
        "scales": {"y": {"beginAtZero": true}}
      }
    }
  }' \
  -o revenue.png

Python

import requests

payload = {
    "width": 900,
    "height": 500,
    "format": "png",
    "chart": {
        "type": "bar",
        "data": {
            "labels": ["Q1", "Q2", "Q3", "Q4"],
            "datasets": [{
                "label": "Revenue",
                "data": [120, 180, 150, 230],
                "backgroundColor": "#4f46e5",
            }],
        },
        "options": {
            "plugins": {"legend": {"display": False}},
            "scales": {"y": {"beginAtZero": True}},
        },
    },
}

response = requests.post("https://quickchart.io/chart", json=payload, timeout=30)
response.raise_for_status()
with open("revenue.png", "wb") as output:
    output.write(response.content)

Node.js

const payload = {
  width: 900,
  height: 500,
  format: 'png',
  chart: {
    type: 'bar',
    data: {
      labels: ['Q1', 'Q2', 'Q3', 'Q4'],
      datasets: [{
        label: 'Revenue',
        data: [120, 180, 150, 230],
        backgroundColor: '#4f46e5'
      }]
    },
    options: {
      plugins: { legend: { display: false } },
      scales: { y: { beginAtZero: true } }
    }
  }
};

const res = await fetch('https://quickchart.io/chart', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Chart request failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('revenue.png', buffer);

Grouped and stacked bars

Use multiple datasets for grouped bars. Each dataset creates a series with its own label and colors. To stack bars, configure the x and y scales with the same stack identifier and enable stacking in the Chart.js options. Keep the number of series small enough that legends and labels remain readable.

"options": {
  "scales": {
    "x": {"stacked": true},
    "y": {"stacked": true, "beginAtZero": true}
  }
}

For a horizontal chart, use the Chart.js index-axis option:

"options": {"indexAxis": "y"}

Output format, size, and styling

Set width and height explicitly so downstream layouts do not depend on defaults. PNG is a safe default for documentation and email. WebP can reduce transfer size when your consumers support it. SVG is appropriate for scalable diagrams, while PDF is useful when the chart is one part of a print document. Use the format supported by your endpoint and verify the response content type before storing it.

Chart.js options control titles, legends, axes, tick formatting, tooltips (where applicable), data labels through supported plugins, colors, borders, bar thickness, and category spacing. Set a zero baseline for counts and financial values unless a nonzero baseline is intentional and clearly labeled.

Image-Charts: generate a bar graph with URL parameters

Image-Charts uses a compact parameterized URL model. Its documented bar types include vertical grouped (bvg), horizontal grouped (bhg), vertical stacked (bvs), and horizontal stacked (bhs). Core parameters identify the chart type (cht), data (chd), and size (chs).

GET request

curl -G 'https://image-charts.com/chart' \
  --data-urlencode 'cht=bvg' \
  --data-urlencode 'chs=900x500' \
  --data-urlencode 'chd=t:120,180,150,230' \
  --data-urlencode 'chl=Q1|Q2|Q3|Q4' \
  --data-urlencode 'chco=4f46e5'

Use URL encoding for every parameter. A URL is convenient for caching and direct embedding, but very long labels, multiple datasets, or elaborate styling can exceed proxy and browser URL limits. In those cases, use the service’s POST form if available.

Colors and rounded corners

The chco parameter supports per-series and per-bar colors according to the Image-Charts documentation. The chbr parameter controls rounded corners. Test colors against the page background and ensure adjacent series have enough contrast. Do not rely on color alone to communicate meaning; labels or patterns improve accessibility.

Choosing between QuickChart and Image-Charts

Requirement Better starting point Reason
Chart.js plugins and detailed options QuickChart You send a familiar Chart.js object.
Short, cacheable chart URLs Image-Charts Chart type, data, size, colors, and labels are parameters.
Grouped or stacked orientation presets Image-Charts The API names vertical/horizontal grouped and stacked variants directly.
Several downloadable formats QuickChart The documented POST endpoint includes PNG, JPG, WebP, SVG, PDF, and base64.
Existing Chart.js code QuickChart Reuse the configuration with minimal translation.

Google ImageBarChart is legacy compatibility information only. Google’s documentation says the Image Charts portion of Google Chart Tools was officially deprecated on April 20, 2012, so it should not be the default for a new integration.

Production implementation checklist

  1. Validate input. Require at least one label and one numeric value per series. Reject NaN, infinite, and mismatched array lengths before making an API call.
  2. Normalize numbers. Decide whether values are counts, currency, percentages, or rates. Format axis ticks and labels consistently.
  3. Set dimensions. Pick dimensions based on the target slot. A wide chart needs more horizontal pixels; a mobile card may need a horizontal bar layout.
  4. Escape labels. Treat labels as data. Encode query parameters and safely serialize JSON.
  5. Check the response. Verify status, content type, and a nonzero body before writing the file. Store an error body separately for diagnostics.
  6. Cache deterministic charts. Hash the normalized request and use that hash as a cache key. Include format, dimensions, theme, and locale in the key.
  7. Protect secrets. Keep service credentials on the server. Do not expose private keys in browser JavaScript or public HTML.
  8. Make retries bounded. Retry transient 5xx responses with exponential backoff and a maximum attempt count. Do not retry malformed requests.

Edge cases that change the result

Large or negative values

Negative bars require an axis that crosses zero. Mixed positive and negative values can make a truncated baseline misleading. For very large magnitudes, use compact tick notation and include the unit in the axis title.

Long labels

Long category names collide on vertical charts. Rotate or wrap ticks if the renderer supports it, increase width, or switch to horizontal bars. Limit label length only when the full value remains available elsewhere.

Missing values

Choose a policy before rendering: omit the category, show a gap, or render zero. Never silently convert missing data to zero when that changes the conclusion.

Many categories

Showing hundreds of bars creates an unreadable image and a large response. Aggregate into a top-N view, paginate, or generate several charts. Preserve the complete dataset in a downloadable table.

Localization

Number separators, decimal marks, currency symbols, and date labels vary by locale. Include locale and timezone in your cache key so one user’s formatting does not appear for another.

Troubleshooting common failures

Symptom Likely cause Fix
400 or 422 response Malformed JSON, missing chart data, or invalid parameters Log the serialized request, validate arrays, and send a minimal known-good chart.
Blank image Empty datasets, transparent colors on a transparent background, or an invalid scale option Render one hard-coded series, set a visible background, and add options incrementally.
Labels are cut off Canvas is too small or labels are too long Increase dimensions, rotate or wrap labels, or use horizontal bars.
Only some bars appear Dataset and label lengths differ Check lengths before serialization and align every value with a category.
Works locally, fails in production Proxy URL limits, blocked outbound traffic, or missing environment variables Use POST, allow outbound HTTPS, and verify runtime configuration.
Intermittent timeout Transient service or network delay Set a client timeout, retry only transient failures, and serve a cached prior image when appropriate.
Wrong format saved Assuming a successful status means the body is an image Inspect Content-Type and reject HTML or JSON error bodies before storing.

Performance, reliability, and cost considerations

Rendering time depends on request size, output format, and service load. POST avoids oversized URLs but can reduce the effectiveness of URL-based caches. Cache immutable chart requests and set an expiration policy for data that changes. For batch reports, queue rendering jobs rather than blocking a user request.

A clean capture removes obstructing overlays before saving the rendered chart page.
A clean capture removes obstructing overlays before saving the rendered chart page.

Use connection reuse in Python and Node.js, set explicit timeouts, and record request IDs or your own correlation IDs. Keep the original chart payload with the generated asset metadata so a chart can be reproduced. Monitor status-code rates, response sizes, and image validation failures. Cost depends on the provider’s current plan and quota; check the service terms before committing to high-volume generation.

Or skip the browser setup

If your chart already exists in a web page, ScreenshotNeo can capture the rendered result with one request. It is useful when the page contains a chart library, custom fonts, or layout logic that would be tedious to reproduce in an image API.

See the ScreenshotNeo documentation for the full option list. The direct call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers identify the page verdict and billing result. An MCP server lets Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account to try it with no card.

FAQ

Can I return SVG instead of PNG?

Yes when the selected service and endpoint support SVG. Confirm the response content type and store the file with an .svg extension.

Should I use GET or POST?

Use GET for short, cacheable requests. Use POST for large configurations, many datasets, or private chart definitions.

Is a stacked chart always better?

No. Stacking shows a total and composition, but grouped bars make side-by-side comparison easier. Pick the layout that matches the question the reader must answer.

How do I make charts accessible?

Provide an adjacent text summary or data table, use sufficient contrast, label units, and avoid conveying meaning through color alone.

When should I render in my own application?

Use local rendering when you need offline operation, strict data residency, or interactive behavior. Use an image API when consistent server-side output and simple HTTP integration matter more.