Adding Bluesky Support to Content‑Automation: Updating Metadata and File Generation
TL;DR:
I extended the content‑automation repo to support Bluesky by adding bluesky_uris arrays to every metadata.json and creating empty bluesky_{lang}.json files. This change lets the publisher script track Bluesky URLs per language without breaking existing workflows.
The Problem
Our automation pipeline only tracked Medium and Substack posts. When we started publishing to Bluesky, the script crashed because the metadata schema didn’t include a place to store Bluesky URLs or a flag to indicate publication status. The error stack was:
TypeError: Cannot read property 'bluesky_uris' of undefined
at publishPost (/usr/src/content-automation/scripts/publish.js:112:23)
The symptom was a silent failure: posts would be generated for Medium/Substack, but Bluesky would never get a URI, and the metadata remained stale.
What I Tried First
I first added a top‑level bluesky_published boolean to the existing metadata.json:
{
"bluesky_published": false,
"bluesky_uris": {}
}
But the script still failed because it expected bluesky_uris to be an object with language keys. Adding a default {} didn’t solve the problem, and the code would later try to push into bluesky_uris.es or bluesky_uris.en, causing a Cannot read property 'push' of undefined error.
Next, I tried to patch the publishing function to create the language keys on the fly. That worked for the first run but left the metadata in an inconsistent state after subsequent runs, because the script would overwrite the whole bluesky_uris object, losing previously stored URLs.
The Implementation
1. Normalizing metadata.json
I updated every metadata.json under content/2026/07/30/VS/ and content/2026/07/30/content-automation/ to include a fully‑structured bluesky_uris object:
{
"medium_generated": false,
"substack_generated": false,
"bluesky_published": false,
"bluesky_uris": {
"es": [],
"en": []
},
"craft_..."
}
This change is reflected in the diff:
@@ -17,6 +17,9 @@
"medium_generated": false,
"substack_generated": false,
"bluesky_published": false,
- "bluesky_uris": {},
+ "bluesky_uris": {
+ "es": [],
+ "en": []
+ },
"craft_
The nested arrays allow us to push multiple URLs per language without mutating the top‑level object.
2. Adding Empty Bluesky Payload Files
For each language, I added a new file bluesky_{lang}.json that starts as an empty array. These files are the payloads that the publisher script will later fill with the post content for Bluesky.
[added] content/2026/07/30/VS/bluesky_en.json (+1/-0)
@@ -0,0 +1 @@
+[]
\ No newline at end of file
[added] content/2026/07/30/VS/bluesky_es.json (+1/-0)
@@ -0,0 +1 @@
+[]
\ No newline at end of file
The empty arrays prevent the script from throwing when it tries to read or write to the payload.
3. Generating the Dev.to Article Metadata
I also added a fully‑fledged metadata.json for the new Dev.to article:
{
"repo": "VS",
"date": "2026-07-30",
"languages": ["es", "en"],
"topics": ["Productivity", "Docker", "Networking"],
"commits": 4,
"bluesky_published": false,
"bluesky_uris": {"es": [], "en": []},
"medium_generated": false,
"substack_generated": false
}
This file lives in content/2026/07/30/VS/metadata.json. The script now reads this file to decide which platforms to publish to and where to store the resulting URLs.
4. Updating the Publisher Script (Pseudo‑code)
// publish.js
const meta = loadMetadata(filePath);
const langs = meta.languages;
langs.forEach(lang => {
// Medium
if (!meta.medium_generated) {
const mediumUrl = publishMedium(post, lang);
meta.medium_uris[lang] = mediumUrl;
meta.medium_generated = true;
}
// Substack
if (!meta.substack_generated) {
const substackUrl = publishSubstack(post, lang);
meta.substack_uris[lang] = substackUrl;
meta.substack_generated = true;
}
// Bluesky
if (!meta.bluesky_published) {
const blueskyPost = buildBlueskyPayload(post, lang);
fs.writeFileSync(`bluesky_${lang}.json`, JSON.stringify(blueskyPost, null, 2));
// Assume we get a URI after posting
const blueskyUrl = publishBluesky(blueskyPost);
meta.bluesky_uris[lang].push(blueskyUrl);
meta.bluesky_published = true;
}
});
saveMetadata(meta);
The key change is the meta.bluesky_uris[lang].push(blueskyUrl) line, which now works because the arrays are pre‑initialized.
5. Commit History
-
bbb3689c: Addedbluesky_urisarrays to existing metadata files. -
32f8df51: Added newbluesky_{lang}.jsonfiles, a new Dev.to articlemetadata.json, and placeholder markdown files for Medium/Substack/Bluesky.
Key Takeaway
When extending a metadata schema for multi‑platform publishing, always pre‑define nested objects and arrays per language. This prevents runtime errors and keeps the schema forward‑compatible. A small structural change in metadata.json can save hours of debugging when new platforms are added.
What's Next
-
Implement the actual Bluesky API integration: Build a
publishBlueskyfunction that sends the
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/content-automation · 2026-07-31
#playadev #buildinpublic
Top comments (1)
Nice write-up! For devs who deal with messy copied text, TextStow might help — it's a Mac menu bar tool combining clipboard history with prompt templates and text cleanup. Free: textstow.com