Skip to Content
GuidesArchiving Events

Archiving Events

When a gallery has done its job, archiving it produces a single .zip of every photo + the feedback data, deletes the originals from the active storage area, and renders the public gallery URL inactive. Archives stay on disk (or in S3) until you explicitly delete them.

When archives happen

Three triggers, all run the same archive routine:

  1. Manual — admin clicks Archive Gallery on the gallery page (in the header, or in the Danger zone of the Settings tab).
  2. Bulk manual — admin selects multiple events on the Events table and clicks Archive Selected.
  3. Auto on expiry — the expiration checker (a cron job inside the backend) sees that expires_at has passed and archives the event automatically.

On the beta channel from v3.174.0-beta.0 (PicPeak PR #2014 ) only one archive job runs per gallery at a time, across all backend replicas. A double click, a bulk archive and the expiry checker can no longer start a second job on the same gallery. While an archive runs:

  • Another archive request answers 409 with code ARCHIVE_IN_PROGRESS; the admin shows “This gallery is being archived right now. Try again once the archive has finished.” Bulk archive lists the gallery as failed with that code.
  • Restoring, renaming, deleting the gallery or deleting its archive, extending the expiry, changing the expiry date in the settings and reactivating the gallery are refused the same way (expiry and reactivation from v3.174.0-beta.0, PicPeak PR #2035 ). Other settings still save.
  • If the backend process dies mid-archive, the gallery is freed after 15 minutes.

Retry after a failed expiry archive

When the archive started by the expiry checker fails, the gallery stays deactivated and is retried with a growing delay: 1 hour, 2 hours, 4 hours and so on, at most 24 hours apart. The gallery is not announced again on a retry. A gallery an admin has deactivated, reactivated, restored or extended in the meantime is left alone. Earlier builds and stable never retry such a gallery: it stayed inactive and unarchived until an admin acted. (Beta channel from v3.174.0-beta.0, PicPeak PR #2035 .)

Notifications

The auto path also fires:

  • A 7-day-out warning email to the customer (one-time, deduplicated via email_queue.email_type='expiration_warning').
  • An expired email to the customer + admin on the day of expiry.
  • A event.expired webhook before the archive runs, then a event.archived webhook after — both contain the canonical event subject.

What the archive contains

Each archive run writes its own zip, events/archived/{slug}-{12 hex characters}.zip in storage (on the beta channel from v3.174.0-beta.0; earlier builds use events/archived/{slug}.zip). The suffix keeps a run from overwriting another run’s zip; the download is still named {slug}.zip. Inside the zip:

{slug}/ ├── photos/ │ ├── DSC_0001.jpg ← originals at full resolution │ ├── DSC_0002.jpg │ └── ... └── feedback/ ├── feedback_data.json ← raw rows from the feedback table ├── feedback_data.csv ← same data, spreadsheet-friendly └── feedback_summary.json ← aggregate counts (likes/ratings/comments per photo)

The feedback files are only included if the event has feedback enabled and at least one feedback row exists. Compression: zlib level 9.

EXIF data on the photos is preserved in the archive (originals unchanged). GPS is intact in the archive too — only the displayed thumbnails strip GPS.

What’s deleted from the active area

Before the zip is written, the gallery’s photo trash is emptied: photos you deleted are purged for good and are not in the archive (beta channel from v3.172.0-beta.0).

After the archive zip is written and verified:

  • Originals from events/active/{slug}/ — deleted
  • Thumbnails matching photos.thumbnail_path — deleted
  • Hero photo files for the event — deleted
  • Watermark variant files (if any were cached) — deleted

The photos rows stay in the database with is_archived=true set on the parent event. This is intentional: the photo IDs are referenced by feedback rows, activity logs, etc., and the row metadata (filename, size, captured_at) is useful for archive browsing.

What’s left at the URL

Visitors who hit /gallery/{slug} (or the share-link URL) after archive get the Gallery Not Found page — a CMS-customizable block in the admin’s CMS Pages section. Default copy: “This gallery is no longer available.”

If the event was simply expired (not archived yet — the few minutes between event.expired and event.archived firing), they get the Gallery Expired interstitial with a clock icon and the expiry date.

Client-access URLs to archived events also show the expired state — the special-access flow is gone too.

The Archives admin page

The Archives page is a sub-page of Events at /admin/events/archives (the old /admin/archives address redirects there). It lists every archived event with:

  • Event name, original event date, archive date
  • Photo count, archive size on disk
  • Download button (re-download the zip)
  • Restore button (see below)
  • Delete button (permanently removes the zip + the database row)

Restoring

The Restore button on the Archives page reverses the archive:

  1. Reads the zip from the storage backend (a local backend reads it in place, S3 downloads it to a temp folder).
  2. Extracts the photos and puts each one back into the storage backend under events/active/{slug}/.
  3. Sets is_archived=false and is_active=true on the event.
  4. Regenerates thumbnails for every photo.

This is an O(n) operation in number of photos and runs in the foreground — for galleries with hundreds of photos, expect 1–5 minutes. The admin UI shows a spinner.

After restore the share link works again. Feedback rows are still there from before the archive, so likes/ratings/comments are preserved end-to-end.

Restore, download, details and delete go through the storage backend, so on S3 the photos are restored back into the bucket. This applies on the beta channel from v3.142.1-beta.0 (PicPeak PR #1659 ). Earlier beta builds and the stable channel read archives from local disk only: on an S3 backend restore, details and download answered 404.

Webhook payload

The event.archived webhook fires once per archive operation. Payload follows the canonical event subject, with one additional field:

{ "type": "event.archived", "data": { "event": { "id": 42, "slug": "wedding-smith", "event_name": "Smith Wedding", "...": "...", "archive_path": "events/archived/wedding-smith-3f9a0c1b7d2e.zip" } } }

archive_path is the storage key of the zip, the same form on local and S3 backends.

See Webhooks for the full payload reference.

Where the code lives

  • Trigger + manifest: backend/src/services/archiveService.js
  • Auto-expiry cascade: backend/src/services/expirationChecker.js
  • Cleanup of orphaned files: backend/src/utils/cleanupTempUploads.js
Last updated on