How multi-platform publishing goes wrong
Content publishing to multiple platforms starts simple: write an article, post it to one place. Then you add a second platform, and a third. Each addition gets its own if-statement, its own hardcoded platform name in the generation route, its own UI card in the admin dashboard.
Six months later, the code looks like this:
// scattered across multiple routes and components:
if (topic === "Tech & AI") {
await generateLinkedInPost(article);
await generateTwitterPost(article);
}
if (topic === "Finance") {
await generateLinkedInPost(article);
await generateMediumDraft(article);
}
// ... and so on
Every new platform requires changes in multiple places. Adding a platform to one category means auditing every file that checks topic names. Removing a platform leaves dead code. The system has no single source of truth for "which platforms does this category use?"
The distribution map
The fix is a declarative map stored in one place:
const DEFAULT_DISTRIBUTION_MAP: Record<string, string[]> = {
"Tech & AI": ["devto", "hashnode", "linkedin", "twitter", "reddit"],
"Finance": ["medium", "substack", "linkedin", "twitter"],
"Health": ["medium", "substack", "linkedin", "instagram"],
"Politics & Society": ["medium", "substack", "linkedin", "twitter", "reddit"],
};
Every generation route, every UI component, and every publishing flow reads from this map — never from hardcoded platform lists.
export function getPlatformsForTopic(
map: Record<string, string[]>,
topic: string
): string[] {
return map[topic] ?? [];
}
Adding a platform to a category is now a one-line change in the map. Nothing else in the codebase needs to change.
The map as the source of per-article workflow state
The distribution map does more than control generation. It becomes the input to a per-article workflow record that tracks what has been generated, what is ready, and what has been published.
function buildDistributionState(article, distributionMap, socialPosts) {
const requiredPlatforms = getPlatformsForTopic(distributionMap, article.topic);
return Object.fromEntries(
requiredPlatforms.map((platform) => [
platform,
{
required: true,
textStatus: hasTextFor(platform, socialPosts) ? "ready" : "missing",
assetStatus: hasAssetsFor(platform, socialPosts) ? "ready" : "not_needed",
publishStatus: "not_ready",
}
])
);
}
The article now knows exactly which platforms it needs, what is done, and what is missing — derived from the map, not from scattered if-statements.
Category changes do not rewrite existing articles
One important rule: if the distribution map changes after an article is saved, the article's existing workflow state should not be silently rewritten.
The map is captured at article-creation time and stored per-article. Later map changes apply to new articles, not retroactively to old ones.
This prevents a frustrating class of bug: you change a category's platform list, and suddenly articles you already published are marked as missing platforms you never intended them to use.
Extending the map to drive UI
Because the map is the source of truth, the admin UI can be generated from it rather than hardcoded:
const platforms = getPlatformsForTopic(distributionMap, article.topic);
return platforms.map((platform) => (
<PlatformCard
key={platform}
platform={platform}
state={article.distributionState[platform]}
/>
));
No hardcoded platform list in the UI. Add a platform to the map, and it appears automatically in the dashboard for that category.
The broader principle
A distribution map is an instance of a general pattern: routing configuration as data, not code. Anywhere you find if-statements that check a category, type, or tag to decide what to do, ask whether those decisions could be captured in a lookup table instead.
The lookup table is easier to read, easier to change, and makes the system's behavior inspectable at a glance — without reading the code.