Guides / Tutorials
Build dog breed pages for a pet website
By the Dog Image API team. Updated .
A useful breed page has text only you can write, a consistent set of images, and clear labels for anything you did not make. Generate the page skeleton from GET /api/v1/breeds in a build step, then add your own content to each page. The skeleton alone is not worth visiting.
What a breed page needs
Start with the reader. Someone lands on a page about a Beagle or a Samoyed. They want to know whether it fits their life, or whether your product fits their dog. A page of a picture and a paragraph copied from an encyclopedia answers neither.
Put your own material first:
- Your own text. What you know from your work: how the breed behaves in your classes, what sizes you stock, which questions your customers ask. One or two honest paragraphs beat six generic ones.
- Consistent images. Use the same view in the same place on every page. A head portrait at the top and a three-quarter view in the body is a layout you set once in the template.
- Clear labels. Say the images are AI-generated illustrations of an adult dog, not photos of a real animal. Say where the summary comes from. A reader who knows what they are looking at trusts the page more.
- Navigation. Link to related breeds and to your own pages: products, articles, contact.
Step 1: read the list
The list needs no key. Each breed has a slug, a name, a crossbreed flag, a default_variant and its variants. Browsers on other sites may read it, but a build script on your computer or CI server is simpler.
Step 2: fetch each breed's detail
GET /api/v1/breeds/{breed} returns the summary (text, url, title, and the whole field may be null) and, for each variant, the image URLs for every view. It needs no key.
Step 3: write the template
The script below uses Node 18 or newer, which has fetch built in. It writes one HTML file per breed into dist/breeds/. It reads the live list, so new breeds appear on the next build. Put your own paragraphs in a content/<slug>.txt file, and the page uses it when it exists.
// build-breed-pages.mjs
import { mkdir, readFile, writeFile } from "node:fs/promises";
const BASE = "https://dogimage.dev";
const esc = (s) =>
String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
async function getJson(path) {
const res = await fetch(`${BASE}${path}`);
if (!res.ok) throw new Error(`${path} failed: ${res.status}`);
return res.json();
}
async function ownText(slug) {
try {
return await readFile(`content/${slug}.txt`, "utf8");
} catch (err) {
if (err.code === "ENOENT") return null;
throw err;
}
}
const { breeds } = await getJson("/api/v1/breeds");
await mkdir("dist/breeds", { recursive: true });
for (const b of breeds) {
const detail = await getJson(`/api/v1/breeds/${b.slug}`);
const variant = detail.variants.find((v) => v.slug === detail.default_variant);
const head = variant.views["head"].preview_512;
const side = variant.views["front-left"].preview_1024;
const mine = await ownText(b.slug);
const s = detail.summary;
const html = `<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<title>${esc(b.name)} | My Pet Site</title></head>
<body>
<h1>${esc(b.name)}</h1>
<img src="${esc(head)}" alt="Illustration of a ${esc(b.name)} head" width="256" height="256">
${mine ? mine.split("\n\n").map((p) => `<p>${esc(p)}</p>`).join("\n") : ""}
<img src="${esc(side)}" alt="Illustration of a ${esc(b.name)}" width="512" height="512">
<p>Illustrations are AI-generated and show no real dog.
<a href="${BASE}/dogs/${esc(b.slug)}">${esc(b.name)} image: Dog Image API</a></p>
${s ? `<blockquote>${esc(s.text)}</blockquote>
<p>Summary from <a href="${esc(s.url)}">${esc(s.title)}</a> on Wikipedia, licensed CC BY-SA 4.0.</p>` : ""}
</body></html>`;
await writeFile(`dist/breeds/${b.slug}.html`, html);
}
console.log(`Wrote ${breeds.length} pages`);
Run it with node build-breed-pages.mjs. The script fetches one breed at a time, which keeps the load small for a few hundred pages. No request limit is stated, so do not parallelize more than you need.
Use the Wikipedia summary correctly
The breed names and color names come from Wikidata, which is CC0. The summaries come from Wikipedia, and Wikipedia text is CC BY-SA 4.0. That has two consequences for your page:
- Credit and link. Name Wikipedia and link the article. The
summary.urlandsummary.titlefields give you both, and the script prints them under the quote. - Share alike. If you change the text or build on it, the result must carry the same license. The simplest path is to quote the summary unchanged, mark it as a quotation and keep your own text separate and under your own terms.
The catalog JSON is also shared under CC BY-SA 4.0. If you republish the breed list itself, the same rules apply.
Because summary can be null, the template above skips the block when there is none. Do not fill the gap with text from elsewhere unless you know its license.
Credit the images
The script uses preview files, which are free for any use with a visible link crediting Dog Image API next to the image or on the same page. The breed page of each breed offers the exact snippet, and the script prints it in that form:
<a href="https://dogimage.dev/dogs/pug">Pug image: Dog Image API</a>
Previews may be hot-linked, as the script does. If you want pages with no credit link, download full-size 2048 pixel PNG files in a build step, using a key from DOG_IMAGE_API_KEY on your build machine only, and host them yourself. Plans and limits are on the pricing page, and the license sets the terms. You may not republish the images as a collection, so build pages around them, not a download library.
Why your own content is the point
A generated page is the same as every other generated page. Search engines and readers have no reason to prefer a skeleton of an image and a quoted summary. The part that makes a page worth a visit is what you add: your tested advice, your products, your region, your photos of real dogs, labeled as photos.
A practical order of work: ship the skeleton for every breed, then write real text for the ten breeds your visitors ask about most, and add more over time. Leave a breed's text empty rather than filling it with filler. For a tour of the endpoints, see the docs. To see how the images look on a finished page, open the Pug page.
Questions
Can I publish the Wikipedia summary as the page text?
Only with credit and a link to the article, and under the same license, CC BY-SA 4.0. It is also thin content by itself, so write your own text first and use the summary as a clearly marked quotation.
Does every breed have a summary?
No. The summary field may be null, so your build must handle a missing summary without breaking the page.
Do I need an API key for the pages?
Not for the breed list, the breed details or the previews. A key is only for full-size PNG files, which you download in a build step and host yourself.
How many pages will this create?
One per breed in the list. The catalog had 298 breeds on 2026-11-14, but read the live count from the response instead of hardcoding it.
Next steps
- API docs: endpoints, keys, limits and errors.
- All dog breeds, each with nine views.
- Pricing for full-size transparent PNG files.
- A dog breed card with Tailwind CSS
- A dog breed gallery in SvelteKit
- A dog breed picker in Angular
- A dog breed selector in Vue 3