
For a while, my photography lived in one place: Instagram. Which is fine, until you realize you don’t actually own that archive — you’re renting shelf space in someone else’s store. I wanted my photos living on my own site, atimefordesign.com, in a format I actually control. Here’s how that migration went, bugs and all.
I did this whole project working alongside Claude, and I want to call that out up front rather than bury it in the middle. This wasn’t “ask AI to write some CSS” — it was a genuinely collaborative debugging process across data cleanup, a live WordPress site, a theme I didn’t fully understand the internals of, and a handful of dead ends that took real back-and-forth to resolve. I think it’s a decent example of what that kind of work actually looks like day to day, mess and all, so I’ve kept those moments in rather than editing them out.
Step one: asking Instagram for my own data back
Instagram lets you request a full export of your account under Settings → Accounts Center → Your information and permissions → Download your information. I requested mine as JSON, which took a few hours to generate, and came back as a zip file containing every post’s caption, timestamp, and image references — going back years.
The raw export isn’t something you can just drop into WordPress. It’s Instagram’s internal format: nested JSON, nested media references, a carousel_media array for multi-photo posts, timestamps in Unix epoch format, and captions still carrying all their original @mentions and hashtags. None of that lines up with what a WordPress import tool expects.
Step two: turning raw JSON into an importable CSV
This is where Claude did the heavy lifting. I handed over the export, and we worked through building a clean CSV with exactly the columns WP All Import needed:
post_id— a simple sequential IDpost_title/post_content/post_excerpt— all pulled from the original captionpost_date— converted from Instagram’s timestamp format into something WordPress could parseimage_count— how many photos were in the postimages— the actual image filename(s)is_carousel— whether the post had multiple photoslatitude/longitude— left blank; I didn’t need geotagging
222 posts came out the other end, ready to import.
Step three: WP All Import, and the bug that took a while to find
I already had WP All Import Pro, so I built a template mapping each CSV column to a WordPress field, and set the featured image to pull from {images[1]} — meaning “just grab the first image in the list.”
That worked great for 206 of the 222 posts. For the other 16 — the ones where is_carousel was yes — it silently broke. Instead of grabbing just the first image, {images[1]} returned the entire comma-separated string of every image in the post, jammed into a single broken src attribute. The result: a broken-image icon instead of a photo.
Rather than guess at which posts were affected, I had Claude pull the actual import history log and parse it for every “not a valid image” warning, which produced an exact list of the 16 broken posts along with every filename involved — instead of me manually clicking through 222 posts to find them by eye. From there it was mechanical: open each post, delete the broken image, insert a proper Gutenberg Gallery block with all of that post’s original images, in order, and move on to the next one.
Step four: making images actually clickable
Once the images were showing correctly, I wanted a lightbox — click a photo, see it full-size in an overlay, not just as a static image in the page flow. I installed Simple Lightbox for that.
It didn’t work at first, and the reason turned out to be interesting: my original WP All Import template was generating bare <img> tags with no wrapping link at all. Lightbox plugins need an <a href="[full image]"> around the image to have anything to intercept. The fix was updating the import template itself:
<a href="[image URL]"><img src="[image URL]" /></a>
Re-running the import (in update mode, so it matched and updated the existing 222 posts instead of duplicating them) pushed that fix out everywhere at once. Lightbox started working site-wide.
Step five: understanding Blocksy’s Content Block system
My theme, Blocksy, has a feature called Content Blocks — essentially reusable template overrides that can replace how specific types of pages render, scoped by conditions like “front page” or “category archive.” My homepage and my Photography category page turned out to be running through two completely separate Content Blocks:
- “Commentary and Photography Home Page” — a full-page override controlling the homepage layout
- “Photography Archive Grid” — a card-level override just for how each photo displays inside the Photography category archive
This mattered more than I expected, because settings that looked like they should apply universally (like Blocksy’s native pagination controls) sometimes only appeared, or only mattered, depending on which Content Block was active on a given page. More than once, a setting I was hunting for in the Customizer was hidden behind an “this page is overridden by a custom template” notice, which sent both me and Claude down the wrong path before we found the actual control. A few times that meant backtracking entirely — Claude proposing a theory about where a setting lived, me screenshotting what I actually found, and both of us revising the theory based on that. It’s a genuinely iterative way to debug an unfamiliar system, closer to pairing with a colleague than looking something up.
Step six: from column-masonry to a real responsive grid
The Photography archive originally used CSS multi-column layout (column-count: 3) for that classic Pinterest-style staggered grid. It looked fine, but it didn’t play well with two things I wanted later: month/year section headings, and predictable per-photo styling.
We rebuilt it as a proper CSS Grid instead — three columns on desktop, two on tablet, one on mobile — with each photo’s card getting consistent white padding, rounded corners, and a soft shadow to match the rest of the site’s card language. Getting photos to size naturally (rather than being force-cropped into squares) took a few iterations: first images were stretching to fill artificial square boxes, then rows were stretching to match their tallest neighbor, then portrait photos were rendering absurdly tall before we capped their height and let them center within the row instead of hugging the top.
Step seven: killing infinite scroll
The category page defaulted to Blocksy’s infinite-scroll pagination, which turned out to be loading photos in an inconsistent order and occasionally re-fetching the same images. Standard numbered pagination was already sitting in the page’s HTML the whole time — Blocksy just layers a JavaScript auto-loader on top of it. Hiding the auto-load trigger element via CSS was enough to fall back to normal clickable page numbers, without needing to touch any JavaScript.
Step eight: month and year headings
I wanted the grid to visually group photos by month, most recent first — like a proper photo journal instead of an undifferentiated wall of images. Since the theme doesn’t do this natively, we added a small JavaScript snippet (via the WPCode plugin) that reads each photo’s post date, detects when the month changes between consecutive cards, and inserts a heading before the first photo of each new month. A MutationObserver keeps it working correctly as more photos load in via pagination.
What we tried and abandoned: cross-post lightbox browsing
One idea I liked in theory: let visitors click a photo on the archive grid and browse through an entire month’s worth of images in the lightbox, post to post, rather than being limited to navigating within a single post’s own images.
Testing this exposed a real limitation. Simple Lightbox depends on a JavaScript object that WordPress generates server-side at page-load time, registering exactly which images are “lightboxable.” Just adding the right data-slb-* attributes to a link via JavaScript wasn’t enough — a quick test confirmed clicking just navigated straight to the raw image file instead of opening the overlay.
What I appreciated here was that Claude didn’t just barrel ahead and write a bunch of speculative code. Before committing to the feature, it flagged that it wasn’t fully certain how the plugin’s grouping mechanism worked, and suggested we run a small, cheap test first — two hardcoded thumbnails, no real logic — to check the theory before investing time in the full month-grouping build. The test failed fast, we learned the real constraint, and I didn’t end up with a half-built feature or a bunch of code to unwind. Making this actually work would have meant hooking into the plugin’s PHP internals, which is a lot of fragility for a nice-to-have. I left it alone, and the site’s better for not carrying that complexity.
Where things landed
222 imported Instagram posts, all rendering correctly. A responsive, evenly-styled photo grid across desktop, tablet, and mobile. Working lightbox on every single post. Clean pagination instead of a glitchy infinite scroll. Automatic month-grouping that makes the whole archive feel more like a journal than a dump.
Going forward, new posts get added the old-fashioned way: request a fresh Instagram archive every couple of months, hand the new export to Claude to build an incremental CSV, and re-run the same WP All Import template. Not fully automatic, but repeatable enough that I don’t dread doing it.
On working with Claude for this
If there’s a takeaway I’d pull out of this project specifically, it’s that the useful version of AI-assisted development wasn’t “generate code and hope.” It was closer to having a patient collaborator who could read a wall of unfamiliar plugin markup, form a real hypothesis about what was happening, and — critically — say “let’s verify this cheaply before we build the whole thing” when the stakes of being wrong were a few wasted hours rather than a broken site. The dead ends (the Customizer setting that didn’t exist where we thought, the lightbox grouping that turned out to be a server-side dependency) were as much a part of the process as the wins, and I’d rather write about the real shape of that work than pretend it was a straight line.
