Can CeylanVienna-based, globally curious.
Learn/Backend

Separate the editorial date from the publish timestamp — they mean different things

Content systems routinely conflate two different concepts: the date the author wrote something, and when it was actually published. Treating them as one field causes sorting bugs, broken date displays, and incorrect analytics. They need to be separate from the start.

2026-04-25·3 min read·beginner

The conflation

Most content systems start with a single date field. It is used for:

  • Displaying "Published on March 15"
  • Sorting the article list newest-first
  • RSS feed timestamps
  • Analytics attribution
  • SEO canonical dates

This works when all content is written and published at the same time. It breaks as soon as you introduce:

  • Drafts — written weeks before they go live
  • Scheduled publishing — date set in advance
  • Backdated content — articles written about past events with an editorial date that predates publication
  • Edits after publication — content updated months later, editorial date unchanged

The two fields

date (editorial date):

  • Set by the author when creating the content
  • Represents the conceptual "when this piece is about" or "when it was written"
  • Can be set to any date — past, present, future
  • Should be stable after initial creation

publishedAt (publish timestamp):

  • Set by the system when the content first becomes publicly visible
  • Represents "when this actually went live"
  • Must be immutable once set — if you unpublish and republish, keep the original
  • Used for sorting, RSS, analytics, and SEO
interface Article {
  date: string;          // editorial date — author-controlled, any value
  publishedAt?: string;  // ISO timestamp — set on first publish, never changed
  published: boolean;
}

Why publishedAt must be immutable

The temptation is to update publishedAt when an article is re-published after edits. This causes two problems:

  1. RSS readers send duplicate notifications. A changed publishedAt looks like a new article to feed readers, re-notifying everyone who already read it.

  2. Analytics lose their baseline. If you are tracking "views in the first 7 days after publication," changing the publish date resets the window. Your engagement data becomes meaningless.

The correct behavior: set publishedAt once, on the first publish event, and never touch it again.

function setPublishedAt(article: Article, now: string): Article {
  if (article.publishedAt) return article; // already set — do not overwrite
  if (!article.published) return article;  // not published — do not set yet
  return { ...article, publishedAt: now };
}

Backfilling legacy articles

If your system was built with a single date field and you are adding publishedAt now, you need to recover the real publish date for existing articles.

The safest approach is to infer it from signals that were recorded at publish time:

async function inferPublishedAt(slug: string): Promise<string | undefined> {
  const [newsletterSent, blogPublished, distributionRecord] = await Promise.all([
    kv.get(`newsletter:sent:${slug}`),
    kv.get(`blog:published:${slug}`),
    kv.get(`article:distribution:${slug}`),
  ]);

  // Return the earliest reliable timestamp we can find
  return earliest([
    newsletterSent,
    blogPublished?.publishedAt,
    distributionRecord?.doneAt,
  ]);
}

This is better than using date as a fallback — the editorial date is author-controlled and may not reflect when the article actually went live.

Which date to show publicly

On public-facing surfaces (article pages, article lists, RSS feeds), prefer publishedAt over date for display and sorting:

  • Sorting should use publishedAt — it reflects the actual publishing order
  • "Published on" display should use publishedAt — it is factually accurate
  • "Written in" or "Originally from" can use date if relevant

The editorial date is useful internally — for organizing drafts, understanding when something was conceived — but it should not drive public-facing behavior unless you have a specific editorial reason.

The broader rule

Whenever a single field is being used to mean two different things, add the second field. The short-term cost of one extra field is far lower than the long-term cost of incorrect sorting, duplicate RSS notifications, and analytics you cannot trust.

More like this, straight to your inbox.

I write about Backend and a handful of other things I actually care about. No schedule, no filler — just when I have something worth saying.

More on Backend

Why parallelising your scraper will get you silently banned

Anti-bot systems don't always return 403s. Sometimes they return empty results — and your logs look clean. Here's why sequential requests are the architecture, not a workaround.

Scheduled publishing without a cron: runtime-evaluated date filters

You don't need a cron job to make content appear on schedule. Evaluate the scheduled date at request time and the content becomes visible automatically — no deployment, no job, no database update required.

Cache AI API results by content hash to prevent cost explosions

Users upload the same image multiple times. AI APIs charge per call. A cache keyed on SHA-256 of the input bytes ensures you pay for each unique input once — not once per upload.

If this raised a question, I'd be happy to talk about it.

Find me →
← Back to Learn