A content status field is only useful if it behaves like workflow logic, not a label.
In an SEO CMS, “draft” and “published” are rarely enough. You need states that describe where an item is in the pipeline, who can act on it, and whether the page is safe to expose to users, search engines, or downstream jobs. If you do not model that explicitly, teams compensate with ad hoc flags like is_ready, needs_edit, live_at, and published_flag, and those drift apart the first time a retry, approval, or partial failure happens.
The cleanest approach is to treat status as a finite lifecycle with a small number of mutually exclusive states, then use timestamps and transition metadata to preserve history. That gives developers something queryable and gives operators something trustworthy.
Start with the states that represent real work
For most SEO publishing systems, the core states are:
draft: content exists, but it is not ready for validation or publication.queued: the item is eligible to be processed by the next stage.reserved: a worker or reviewer has locked it temporarily.generated: structured output has been produced, but it has not passed review.needs-review: the item requires human or automated review before it can move forward.published: the canonical live version is visible and indexed like any other page.failed: the item hit a terminal or retryable error.archived: the item is intentionally retired from active use.
That set is enough to model most workflows, but it is not exhaustive. Some systems also need scheduled, pending-publish, blocked, suppressed, deleted, or redirected. Add them only when they describe a distinct operational state. Do not create a separate status for every possible mistake.
A useful test: if two pages in the same state need the same next action, they can share a status. If they need different handling, they probably should not.
Keep status separate from content quality
Status should describe workflow position, not editorial merit.
A page can be generated and still low quality. A page can be published and later become stale. A page can be archived because it is obsolete, not because it failed validation. If you mix those concerns into one field, you lose the ability to ask simple questions like:
- Which pages are waiting for review?
- Which pages failed generation versus failed validation?
- Which published pages are stale but still indexable?
- Which archived pages should be redirected rather than removed?
If you need to track quality, add separate fields such as quality_score, validation_status, review_outcome, or retention_reason. This is the same principle described in how to store SEO metadata in a database: the schema should preserve meaning instead of collapsing everything into one overloaded column.
Make transitions explicit
The most common failure mode in content systems is allowing arbitrary state changes. A record jumps from draft to published, then someone later marks it needs-review, then a worker retries generation and overwrites the live content. That is how state drift begins.
Instead, define allowed transitions:
draft → queuedqueued → reservedreserved → generatedgenerated → needs-reviewneeds-review → queuedorneeds-review → publishedpublished → archivedfailed → queuedorfailed → archived
You can also allow some transitions to skip steps in controlled cases, but those exceptions should be visible in code and logged. A transition table or state machine makes that logic auditable. It also gives you a place to enforce invariants such as:
- only one active worker can reserve a record at a time,
- only reviewed content can be published,
- only published content can be indexed in production sitemaps,
- archived content cannot re-enter the normal queue without an explicit restore action.
This is also where how to separate content generation from publishing becomes relevant: the system should preserve a non-public intermediate state before a page can become live.
Use reservation as a lock, not a status dump
reserved deserves special treatment.
Reservation is usually temporary and operational. It means “someone is working on this now,” not “this is the long-term state of the content.” If you model it as a proper status, keep it short-lived and paired with lock metadata:
reserved_byreserved_atreservation_expires_atreservation_reason
That lets workers recover from crashes, expired locks, and abandoned jobs. It also prevents duplicate processing in systems that generate or validate pages asynchronously. The same idea applies to queue design in how to build a content generation queue for SEO and to worker safety in how to process SEO content jobs reliably.
A reservation should never be the only thing preventing double publication. The database should still enforce uniqueness on the live URL or content identity where needed.
Treat failure as a first-class branch
failed should not mean “we gave up and deleted the row.” It should preserve enough detail to explain what happened and what to do next.
At minimum, store:
failure_typesuch as validation, generation, publish, deploy, or syncfailure_codeor machine-readable reasonfailure_messagefailed_atretry_countlast_retry_atcan_retry
Not every failure is terminal. A model timeout, API quota issue, or temporary database lock may belong back in queued. A malformed source record or missing dependency may need human correction first. This is where a repair path matters, and why a separate repair queue for failed SEO content is usually better than retrying everything blindly.
If your system uses retries, make sure the state after retry is deterministic. A failed item that retries into a new generated version should not keep the old artifact as if it were still current.
Published is not the end of the lifecycle
published means the current live version is the active one, not that the page is done forever.
Published records usually need additional fields beyond status:
published_atcanonical_urlrendered_atordeployed_atindexableorrobots_policylast_checked_atnext_refresh_due_at
A page can be published but temporarily excluded from indexing if deployment is delayed, the canonical is wrong, or the page is intentionally noindexed. Do not infer these states from published alone.
If you are building a larger system, publication should also trigger sitemap inclusion, internal link updates, and post-publish monitoring. That is part of the broader publishing lifecycle described in SEO publishing workflows: from draft to indexed page and how to track SEO content from generation to indexing.
Archived should be intentional, not a trash bin
Archival is different from deletion.
Use archived when a page is no longer part of the active library but should remain in the system for auditability, restoration, or historical reporting. Archived pages may still need redirects, canonical cleanup, sitemap removal, and internal-link pruning. They should simply be excluded from normal generation and publishing flows.
That distinction matters when content is refreshed or retired. A page that is no longer strategically useful may be archived, redirected, or deleted depending on whether it still has link equity, inbound references, or a replacement destination. The status alone should not decide that; the workflow around it should.
Design for queries, not just writes
A status model is only valuable if the team can query it cleanly.
Examples of useful queries include:
- all
queueditems older than 24 hours, - all
faileditems withcan_retry = true, - all
publisheditems whose refresh date has passed, - all
needs-reviewitems assigned to a specific reviewer, - all
archiveditems that still have live internal links.
That means the schema needs consistent timestamps, indexed status columns, and no ambiguous reuse of the same field for different meanings. For higher-scale systems, combine the status table with the broader content record design in database schema for a scalable SEO content system and the content model guidance in how to model SEO content in a CMS.
A practical rule for implementation
If a status change affects:
- who can act on the record,
- whether the page is public,
- whether downstream jobs should run,
- or whether the record should be counted in reporting,
then that change deserves a formal state transition.
If it only adds nuance for humans, it can often be a separate field, note, or metadata flag.
That separation keeps the CMS predictable. It also keeps the publishing pipeline debuggable when generation, review, deployment, and indexation all happen at different times and in different services. A good status model does not just describe content. It keeps the system from lying about where the content actually is.