---
title: Add Hosted Images to Markdown and README Files
description: Upload an image once, add a public immutable URL with useful alt text, and verify a README change.
slug: host-images-markdown-readme
date: 2026-08-20
updated: 2026-08-20
last_tested: 2026-08-20
summary: Upload one image, copy its public URL, and insert it with non-empty Markdown alt text in a README.
cluster: Automation workflows
intent: workflow
sources:
  - title: GitHub basic writing and formatting syntax
    url: https://docs.github.com/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
  - title: imgd.dev OpenAPI document
    url: https://imgd.dev/openapi.json
  - title: imgd.dev agent reference
    url: https://imgd.dev/llms.txt
---

# Add Hosted Images to Markdown and README Files

Upload the image once, read `url` from the JSON response, then use `![useful alt text](public-url)` in the Markdown file. Repository readers can fetch the public URL without an imgd.dev key.

imgd.dev charges a one-time **$1 per GB** of storage. It has **no free tier**, so fund the account before the first upload. Every imgd.dev image is public to anyone with its URL. Do not use this method for private screenshots or confidential documentation.

## When an external URL helps

A content-addressed image URL does not change while the stored bytes remain available. This property fits README files, release notes, issue templates, and public reports.

GitHub recommends relative image paths for images stored in the same repository. Use a relative path when repository storage and repository permissions fit your needs.

Use an external URL when you want one public image shared across several repositories or documents. The image remains outside the Git repository history.

## Upload the image

Run these commands from the repository root. Keep `IMGD_KEY` in the environment and outside version control.

```bash
set -euo pipefail
test -n "$IMGD_KEY"

response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
http_status="$(
  curl --silent --show-error \
    --output "$response_file" \
    --write-out '%{http_code}' \
    -X POST https://imgd.dev/v1/upload \
    -H "Authorization: Bearer $IMGD_KEY" \
    -F "file=@docs/images/request-volume.png;type=image/png"
)"

if [[ "$http_status" != "200" && "$http_status" != "202" ]]; then
  jq . "$response_file" >&2
  exit 1
fi

cp "$response_file" upload.json
jq . upload.json
```

A new image returns HTTP `202` with this complete JSON shape:

```json
{
  "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "url": "https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "status": "processing",
  "mime": "image/png",
  "bytes": 18452,
  "width": 1200,
  "height": 800,
  "alt_text": null,
  "unpublish_at": null,
  "note": "the url works immediately, serving a blurred placeholder until the moderation check finishes"
}
```

An identical upload can return HTTP `200`. That response also contains `"deduplicated": true`.

The URL works immediately. It can show a blurred image during moderation. The same URL shows the final image after a `live` result.

## Insert the Markdown image

Validate the returned host and hash before you edit the README:

```bash
IMAGE_URL="$(
  jq -er \
    '.url | select(test("^https://i\\.imgd\\.dev/i/[0-9a-f]{64}$"))' \
    upload.json
)"
printf '%s\n' "$IMAGE_URL"
```

Use specific alt text that gives the image meaning. Do not leave the alt text empty.

```markdown
![Line chart of monthly API requests from January through June](https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef)
```

Append the image from the command line when that edit fits your file:

```bash
README_PATH="README.md"
ALT_TEXT="Line chart of monthly API requests from January through June"
printf '\n![%s](%s)\n' "$ALT_TEXT" "$IMAGE_URL" >> "$README_PATH"
```

The file path is relative to the repository root. Use another relative path, such as `docs/report.md`, when you edit a nested document.

## Verify the README change

First, verify the public image response:

```bash
curl --fail --silent --show-error \
  -D image.headers \
  -o /dev/null \
  "$IMAGE_URL"
grep -i '^content-type: image/' image.headers
```

Then inspect the exact Markdown change:

```bash
git diff --check -- README.md
git diff -- README.md
```

Use the GitHub preview before you commit. Confirm that the image loads and the alt text describes its purpose.

A repository reader does not send `IMGD_KEY`. The reader fetches the public image URL directly, or through a platform image proxy.

## Verify the image metadata

Use the authenticated metadata endpoint when you need the moderation state and generated alt text:

```bash
HASH="$(jq -r '.hash' upload.json)"
curl --fail --silent --show-error \
  -H "Authorization: Bearer $IMGD_KEY" \
  "https://imgd.dev/v1/images/$HASH" | jq .
```

A live result can contain this JSON:

```json
{
  "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "url": "https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "status": "live",
  "mime": "image/png",
  "bytes": 18452,
  "width": 1200,
  "height": 800,
  "alt_text": "A line chart that shows monthly API requests rising from January through June.",
  "category": "safe",
  "nsfw": false,
  "violence": false,
  "filename": "request-volume.png",
  "unpublish_at": null,
  "published": true,
  "created_at": 1787184000
}
```

A `review` result is terminal and stays blurred. A `blocked` result removes the bytes. Remove a blocked URL from the README.

## Recover from upload errors

Each API error contains a stable `error` value and a direct `fix` instruction.

| Status | Meaning | Recovery |
| --- | --- | --- |
| `401` | The key is missing or invalid. | Set the correct `IMGD_KEY`. Do not put the key in the Markdown. |
| `402` | The account has no storage. | Buy storage and confirm funding, then retry. There is no free tier. |
| `402` | The image exceeds the account quota. | Add the suggested storage or delete unused images, then retry. |
| `413` | The file exceeds 20 MB. | Resize or compress the image, then retry. |
| `415` | The media type is not accepted. | Use JPEG, PNG, GIF, WebP, or AVIF with the matching type. |
| `429` | The account exceeded the upload rate. | Wait for the next minute, then retry. |

A representative `415` response is:

```json
{
  "error": "unsupported_media_type",
  "fix": "send jpeg, png, gif, webp, or avif and set the matching Content-Type",
  "accepted": ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif"]
}
```

Do not retry a `401`, `413`, or `415` response without the required change.

## Security and maintenance notes

- Keep `IMGD_KEY` in an environment variable or a secret manager.
- Never put the key in Markdown, a URL, Git history, or a log.
- Remove sensitive EXIF or location data before upload.
- Treat the image URL as public, even in a private repository.
- Use useful alt text for readers who cannot load or see the image.
- Keep the original local source under your normal backup policy.
- Delete the imgd.dev image when you must free its storage quota.

Deleting the image makes the public URL stop serving. A README that still uses that URL will show a broken image.

## When imgd.dev fits

imgd.dev fits public project images that need one stable URL across several Markdown files. It also avoids binary changes in the Git history.

It does not fit private images, repository-only access, a free trial, video, or files above 20 MB. Store sensitive images in an access-controlled system.

## Next guides

- [Post a hosted CI screenshot in a pull request](/blog/github-pr-screenshot-comment/).
- [Host an image for HTML email](/blog/host-images-html-email/).
- [Resize and convert a hosted image](/blog/resize-convert-image-webp-avif/).

For an agent, follow the [integration procedure](/integrate.md). For human setup, open [the start page](/#start). Read the [service evaluation](/evaluate.md) before you select a host.

## Sources

- [GitHub basic writing and formatting syntax](https://docs.github.com/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
- [imgd.dev OpenAPI document](https://imgd.dev/openapi.json)
- [imgd.dev agent reference](https://imgd.dev/llms.txt)
