Case study · Building
plants.place: an encyclopedia where the model has no right to facts
A houseplant encyclopedia in Ukrainian and English and a classifieds market for plants, built solo on Cloudflare. The interesting part isn't the code — it's the rules that keep AI-written pages honest.
plants.place- Role
- Everything: product, SEO, content system, code via Claude Code
- Launch
- Aug 2026 · 211 commits in the first 9 days
- Stack
- One Cloudflare Worker, no framework · D1 · R2 · Turnstile
- Today
- 133 species · 54 listings · Ukrainian + English

The gap
It started at home. My partner keeps houseplants and couldn’t find proper botany in Ukrainian — not even by Latin name. I checked. Search for a plant’s Latin name in Ukrainian and you get shops and Wikipedia. Nobody had structured botany in Ukrainian: origin, size, toxicity for pets, care — in one place, from reliable sources.
Before writing a line of code, we mapped the demand. Three Ahrefs exports for Ukraine gave 375,863 keywords. After cleaning, 20.9% of the search volume turned out to be junk, and the rest folded into 345 topics — one per plant, not one per phrase.
Some of the junk was a sign of the times. “Gerbera” and “geranium 2” are also names of military drones: 2,906 junk keywords and about 79,000 searches a month came from war-related queries. The biggest junk category was food and raw materials — lavender, avocado and rosemary without any plant context.
The rest of the demand was clear: about 78% of searches are just the plant’s name. “Buy” and “price” are around 8%, care questions around 7%. People want to know the plant first.
What people search for about houseplants in Ukrainian
Just the plant name 78%, buy and price 8%, care 7%, everything else 7%.
The rule: the model has no right to facts
Latin name, author, family, size, native range, toxicity — none of it is generated. These facts come from GBIF, Wikipedia and POWO and go into the prompt as data. The model writes only the connecting text around them. No fact — the field stays empty.
It’s not pedantry. The very first generation placed the monstera in “the tropical forests of the Amazon”, because Ukrainian Wikipedia says so. The species comes from Central America. Without the rule, a source’s mistake would have gone into the encyclopedia as a fact, under our name. Now the verified-facts block has priority over everything, including Wikipedia.
Asking nicely doesn’t work. A list of complaints does.
You can write “at least two blocks after every heading” in the prompt as many times as you like. The model will still put one paragraph there. What works is checking the answer with code, collecting a concrete list of complaints, and sending it back to the model as the next message — together with its own previous answer. Three attempts at most.
The checks are boring on purpose: enough text after each heading, at least four sections, length limits for the lead and the meta tags, at least one list, no markdown asterisks, no phrases like “according to verified data” that sound like an excuse to a reader who never sees our data table.
On the first 50 cards the result was measurable: on average 29 blocks and 554 words per card, 73 tables, 205 lists, and not a single complaint on the final run.
| Metric | Cheaper model | Stronger model |
|---|---|---|
| Words, same card (monstera) | 277 | 493 |
| Tables | 0 in 20 cards | 73 in 50 cards |
| Attempts to pass checks | 1–3 | first try |
| Cost per species | $0.007 | $0.065 |
The stronger model costs about $3 more for 50 cards. The cheaper one saves pennies and costs you the quality you later pay for in editing.
27% of stock photos showed the wrong plant
Photos come from stock libraries through their API, with the author and license under every picture. A search by Latin name often finds nothing; a search by common name finds the wrong thing. When we checked the live catalogue, 122 of 447 photos — 27% — didn’t show the right plant. One species page had a supermarket on it.
Stock photos in the live catalogue, checked one by one
447 photos: 325 showed the right plant, 122 did not.
So the last line of defence is not the search query but a look: every photo goes through a vision model before it’s saved, and doubt means “no”.
Name it the way people search
A botanically perfect card can get zero impressions if it’s named with the wrong word. In Ukrainian, people search «герань» (geranium) 32,990 times a month and «пеларгонія» (pelargonium) 2,980 times. The card is now titled “Pelargonium (geranium)”: correct and findable. It is also the most-shown page on the site. And “money tree” turned out to mean four different plants in Ukrainian, while in English it means just one of them — so cards that people confuse now point to each other.
The market that wasn’t in search
The plan for the classifieds part relied on “110,000 impressions on ‘buy’”. A closer look showed that private, person-to-person demand in search is tiny — about 3,200 impressions. The rest is people looking for a shop. Plants are traded hand to hand in Facebook and Telegram groups, which people visit directly.
So the market pages are closed from indexing, and listings appear on the species cards instead. There was also a tempting shortcut — filling the market with listings scraped from other boards. We didn’t take it: duplicate content, personal data, broken platform rules, and a visitor who writes to a “seller” that never answers.
Going English
Where to grow next was decided by a probe, not intuition: one API request per country, 32 countries. English-language markets together showed about 1.07 million probe searches — roughly four times more than the next language, French, with 272,000. The encyclopedia scales by language; the market scales by country.
For translation, a pilot on two cards named the cheapest model the winner. A blind test on ten cards, judged by another model, picked a different one, 8 to 2. Conclusions only from a proper sample.
The mechanics are plain. English now lives at the root and Ukrainian under /ua/. Every species page lists both versions plus an x-default, and each language has its own sitemap with the same links. A page without a translation returns a 404 in that language and stays out of hreflang and the sitemap, so Google never finds a link that doesn’t point back. 201 old addresses redirect to the new ones with one 301 each. One trap to remember: the folder is /ua/, but the language code is uk — “ua” is the country.
Is this programmatic SEO?
Yes: 133 species pages from one template and one pipeline. What keeps it from becoming the mass-produced kind is four limits. Facts come only from botanical databases. Code checks every answer and sends a list of complaints back. The prompts were calibrated on cards I read by hand before the pipeline ran on its own. And every photo is checked before it goes live — because 27% of them were wrong.
Results
plants.place in Google Search: impressions per month
August 1,662 impressions and 9 clicks; September 3,864 impressions and 22 clicks.
Two early signals: 78 pages were in Google’s index a week after launch, and the first search impressions came on the third day of the domain’s life.
What I’d do differently
- Build the design system as a live page from day one. An audit later found 47 font sizes where 6 were planned, and page headings implemented in nine different ways.
- Check the domain’s past before launch. Search Console’s crawl stats showed Google asking for WordPress addresses — a site we never had.
- Run a logic audit earlier. The “shade-tolerant” filter returns 68 of 103 species, including plants that need bright light. It needs a real shade-tolerance field, not a word match, and that fix is still on the list.