I run a small web agency in Greece, and on the side I build niche tool sites. My latest one is Fortnite Account Value: free calculators that show what a Fortnite locker would cost in the Item Shop, how rare each skin is, and what's in the shop today.

It started as four calculators. This week it grew into a daily Item Shop tracker with full history for thousands of items, share images, three currencies and a Chrome extension. Here's how it's built and what I learned.

The stack: boring on purpose

  • PHP 8.1+, MySQL 8, vanilla JavaScript
  • No framework, no build step, no npm on the server
  • One front controller (index.php), one folder per tool (tools/<slug>/), a small admin panel and a web installer

Why no framework? These sites run on cheap shared hosting, I build several of them on the same skeleton, and I want to upload a folder over FTP and be done. A 60-line router and a few static classes (Database, Settings, Page) cover everything I need.

Data: one sync, every day

Cosmetics come from Fortnite-API.com, an unofficial community API. A cron job hits a secret URL just after the shop resets at 00:00 UTC:

curl -s "https://fortniteaccountvalue.com/cron/sync/?key=SECRET" 

The sync imports every cosmetic type (outfits, pickaxes, emotes, gliders…), then reads today's shop. Originally I only used the shop to update a "last seen" date and threw the line-up away. That turned out to be the most valuable data I had.

Now every run stores the whole day: each offer, its section, its price, whether it's a bundle, and the items inside it. Each item also keeps its full list of shop dates. With that I can show things people actually search for:

  • Today's Item Shop, with a "Back after 2.4 years" or "New" label on every item
  • A page for every past day (/item-shop/2026-11-03/) and a calendar of all of them
  • A year × month history grid on every item page, with its longest gap
  • "Longest gone from the shop" lists per item type

Keeping the history as a comma-separated TEXT column instead of a separate table sounds lazy, but it means one row per item, and appending today's date is a single UPDATE:

UPDATE cosmetics SET shop_dates = CASE WHEN shop_dates IS NULL OR shop_dates = '' THEN ? WHEN LOCATE(?, shop_dates) = 0 THEN CONCAT(shop_dates, ',', ?) ELSE shop_dates END WHERE id = ?