Publishing Lifecycle
Publishing Lifecycle
ModulaCMS content goes through a lifecycle from creation to publication. Content starts as a draft, can be published immediately or scheduled for a future time, and can be reverted to any previous version. Every publish operation creates an immutable version snapshot that preserves the content's field values at that point in time.
Content Status
The ContentStatus type has two values:
| Status | Description |
|---|---|
draft |
Content is being edited. Not visible through the public content delivery API. |
published |
Content is live and served by the public content delivery API. |
Content is created with status: "draft" by default. The publish_at timestamp field on content data supports scheduling.
Publishing
Publishing transitions content from draft to published. The operation:
- Sets the content node's status to
published. - Records
published_at(current timestamp) andpublished_by(the user performing the action). - Creates an immutable version snapshot of the current field values.
- Increments the content's
revisioncounter.
resp, err := client.Publishing.Publish(ctx, modula.PublishRequest{
ContentDataID: contentID,
Locale: "en", // optional; default locale if omitted
})
// resp.Status == "published"
// resp.VersionNumber == 1
// resp.ContentVersionID == "01ABC..."
Published content is immediately available through the public delivery endpoint (GET /api/v1/content/{slug}).
Unpublishing
Unpublishing reverts content back to draft status, removing it from public delivery. Existing version snapshots are preserved.
resp, err := client.Publishing.Unpublish(ctx, modula.PublishRequest{
ContentDataID: contentID,
})
// resp.Status == "unpublished"
The content's published_at and published_by fields are cleared. The content is no longer served by the delivery API until republished.
Scheduling
Scheduling sets a future publication time. The content remains in draft status until the scheduled time, when the server automatically publishes it.
resp, err := client.Publishing.Schedule(ctx, modula.ScheduleRequest{
ContentDataID: contentID,
PublishAt: "2026-04-01T09:00:00Z", // ISO 8601 timestamp
})
// resp.Status == "scheduled"
// resp.PublishAt == "2026-04-01T09:00:00Z"
The publish_at field on the content data node stores the scheduled time. The server checks for scheduled content and publishes it when the time arrives. Scheduling creates a version snapshot at the time of the schedule call.
To cancel a scheduled publish, update the content data node and clear the publish_at field.
Version Snapshots
A version snapshot is an immutable record of a content node's field values at a specific point in time.
type ContentVersion struct {
ContentVersionID ContentVersionID `json:"content_version_id"`
ContentDataID ContentID `json:"content_data_id"`
VersionNumber int64 `json:"version_number"`
Locale string `json:"locale"`
Snapshot string `json:"snapshot"`
Trigger string `json:"trigger"`
Label string `json:"label"`
Published bool `json:"published"`
PublishedBy *UserID `json:"published_by,omitempty"`
DateCreated Timestamp `json:"date_created"`
}
| Field | Purpose |
|---|---|
VersionNumber |
Incrementing version counter per content node |
Locale |
The locale this snapshot applies to |
Snapshot |
JSON string containing all field values at the time of creation |
Trigger |
What created the version: "publish", "manual", "restore", "schedule" |
Label |
Optional human-readable name (e.g., "Pre-launch draft") |
Published |
True if this version is the currently live version for the given locale |
Automatic Versioning
Versions are created automatically when you:
- Publish content -- trigger is
"publish" - Schedule content -- trigger is
"schedule" - Restore content -- a snapshot of the current state is saved before overwriting (trigger is
"restore")
Manual Versioning
Create a version snapshot without changing publish status:
version, err := client.Publishing.CreateVersion(ctx, modula.CreateVersionRequest{
ContentDataID: contentID,
Label: "Before redesign", // optional
})
This saves a checkpoint of the current field values that you can restore later.
Listing Versions
versions, err := client.Publishing.ListVersions(ctx, contentID.String())
// versions is ordered newest-first
Deleting Versions
err := client.Publishing.DeleteVersion(ctx, versionID)
Deleting a version removes the historical snapshot. It does not affect the current content.
Restoring a Version
Restore replaces the current draft field values with those from a previous version snapshot. The content's publish status is not changed.
resp, err := client.Publishing.Restore(ctx, modula.RestoreRequest{
ContentDataID: contentID,
ContentVersionID: versionID,
})
// resp.FieldsRestored == 5
// resp.UnmappedFields == ["old_field_name"] // fields in snapshot but not in current schema
The restore operation:
- Creates a version snapshot of the current state (so you can undo the restore).
- Reads the field values from the specified version's
SnapshotJSON. - Overwrites the current content field values with the snapshot values.
- Returns the count of restored fields and any unmapped fields.
Unmapped fields are field names present in the version snapshot but absent from the current datatype schema (because the schema changed after the snapshot was created). These values are skipped during restore.
After restoring, the content is still in its current publish status. Call Publish separately to make the restored content live.
Admin Content Publishing
The admin content system has its own parallel publishing lifecycle with identical operations:
| User Content | Admin Content |
|---|---|
PublishRequest |
AdminPublishRequest |
PublishResponse |
AdminPublishResponse |
ScheduleRequest |
AdminScheduleRequest |
ContentVersion |
AdminContentVersion |
RestoreRequest |
AdminRestoreRequest |
Admin publishing is accessed via client.AdminPublishing and operates on the admin content tables.
Lifecycle Summary
+-- Schedule ---> [publish_at set] ---> auto-Publish
|
Create (draft) ---> Publish ---> [published]
^ |
| +-- Unpublish ---> [draft]
|
+-- Restore (from version) ---> [draft, fields overwritten]
Every state transition that changes content visibility creates a version snapshot. The version history is append-only and survives unpublish, restore, and republish cycles.