What to expect
- Migrations run on startup, as in v5. One v6 migration adds columns and tables, moves data, and drops two legacy columns. You don’t run anything by hand.
- v5 can’t run against the upgraded database. The v6 migration removes columns that v5 reads, so stop every v5 instance before the first v6 instance starts. There’s no mixed v5 and v6 rolling update.
- Cover letters leave resumes. Every letter stored inside a resume becomes a saved letter of its own.
- Old style-editor styles need a one-time manual conversion, with a script you run yourself. Nothing converts them automatically.
- New optional environment variables configure server AI and web access. Existing v5.3 settings keep working. Reverse proxies need
TRUSTED_PROXIESto preserve per-visitor authentication limits;REDIS_URLis now optional for the assistant outside Vercel. - Vercel projects must switch to the Services framework preset before they deploy v6.
Before you upgrade
1
Back up the database and files
Take a full PostgreSQL backup (for example with
pg_dump) and back up your uploads: the local storage directory,
the S3 bucket, or the Blob store. Check that you can restore the backup. A backup is the simplest way back if
anything goes wrong; see Roll back to v5.2
Check your integrations
If scripts, automations, or AI clients use the API or MCP server, read API changes and MCP
changes. Some fields and tools were removed.
3
Plan a short downtime
Migrations usually finish in seconds, but the letter migration scans every resume. On large installations, allow a
few minutes and upgrade when traffic is low.
Upgrade
- Docker
- Kubernetes
- Vercel
Change the image tag to the v6 release (Use your own service name if it isn’t
v6, a specific version such as v6.0.0, or latest), then recreate the container:reactive-resume. Watch the log for Running database migrations... followed by Database migrations completed. The server starts serving only after that.Building from the repository’s compose.yml instead? See Self-hosting with Docker for the build command.Check the upgrade
- Open
/api/health.statusshould behealthyandversionshould start with6. - Sign in. You land on Documents, which lists resumes and cover letters together.
- If a resume used to contain a cover letter, that letter now appears in Documents as a document of its own, named after the resume.
- If the server log shows
Database schema verification failed, read Schema check.
Convert styles from the old style editor
Before Custom Styles became CSS, v5 had a visual style editor that stored its own style rules. v6 no longer renders those rules. Until you convert them, resumes and letters that still use them print with their template’s default look. The conversion rewrites each affected document’s stylesheet as Custom Styles (CSS) that reproduces the old look. It runs only when you start it, it works in four tables (resume, resume_version, cover_letter, cover_letter_version), and it changes only the stylesheet: the old rules stay in the data. Files imported later from old JSON exports are converted in the browser as they’re imported, so they don’t need the script.
The script is apps/server/dist/migrate-legacy-styles.mjs, included in the Docker image. It connects with the server’s DATABASE_URL.
1
Dry run
Converts every affected row in memory and prints counts. Nothing is written.
2
Convert, with a backup file
Every stylesheet it replaces is appended to the backup file (one JSON object per line) before its row is written. Put the file on persistent storage, such as the It’s safe to run again, for example after an interruption: converted rows are skipped. You can reuse the same backup file or start a new one.
/app/data volume.3
Undo, if needed
Puts back the stylesheets recorded in the backup file, except on documents whose stylesheet was edited since.
On Kubernetes, run the same commands with
kubectl exec deploy/<your-deployment> -- node apps/server/dist/migrate-legacy-styles.mjs .... On Vercel there’s no container: from a checkout of the same release, put the production variables in the root .env (for example with vercel env pull .env --environment production), set DATABASE_URL to the direct, unpooled connection, run pnpm install and pnpm build --filter=server, and then run the script with node.
Check legacy PostgreSQL volumes
Some v5 Compose examples mounted PostgreSQL 17 at/var/lib/postgresql. Unless you explicitly changed PGDATA, PostgreSQL 17 stores its database at /var/lib/postgresql/data, so Docker created an anonymous volume there instead of using the parent mount. See the official PostgreSQL image volume guidance.
Before recreating that container, inspect its mounts and make a logical database backup from the running instance. Keep the old container and its volumes until you have restored and checked the backup. Do not run docker compose down -v or delete anonymous volumes during the upgrade.
PostgreSQL 18 changes the default data layout. Changing the image tag alone does not migrate a PostgreSQL 17 database; restore the logical backup into a fresh PostgreSQL 18 volume, or follow PostgreSQL’s supported major-version upgrade procedure.
What the migrations change
Existing v5 migrations stay unchanged. The v6 changes are consolidated in20261001042749_v6_release, generated from the current schema with the v5 data conversions included once. It runs under an advisory lock and ships one rollback.sql alongside migration.sql.
Cover letters leaving resumes
In v5, a resume could hold cover letters as a section. The v6 migration moves each of them, hidden ones included, into a saved letter:- The letter is named after the resume and the section, for example “Product Designer — Cover letter”.
- It’s linked to the resume, and takes its sender details and design from it, so it looks as it did inside the resume.
- If a resume held exactly one letter and exactly one application used that resume without a letter, the letter becomes that application’s letter.
- Resume version history keeps its older versions as they were. Restoring one saves its letters again as letters (once) instead of putting them back in the resume.
Structured dates
Dates on entries (experience and its roles, education, projects, volunteering, awards, certifications, and publications) are now stored as structureddates: a start and end year or year-month, and whether it’s ongoing. No migration changes stored data for this. Instead, every read fills in dates from the old text, and every save writes the text (period or date) from dates. Text that couldn’t be read exactly, such as “Summer 2016”, keeps printing as typed until someone edits the date. The date format is inferred from how the dates were typed. See Entering dates.
Trash
Deleting a resume or letter now moves it to Trash. Trashed documents stop being shared publicly, and their public address stays reserved. They’re deleted for good, with their files, 30 days later. There’s no scheduled job for this: it happens the next time the owner opens their documents, so storage is freed then. See Using the Trash.Removed and redirected routes
These v5 addresses redirect to their v6 place, so bookmarks and old links keep working through the 6.0 releases. Update any links you control.
Cover letters now open in their own editor at
/builder/letter/<id>. API, MCP, auth, and public resume addresses (/<username>/<slug>) are unchanged.
API changes
Base paths, authentication, and API keys are unchanged. These changes can affect existing clients. The API reference has the full list. Resume data- Dated items carry
dates(start,end,present, andrawfor text that couldn’t be read). Writedates. The text inperiodordateis rewritten fromdateson every save, so a change to the text alone is overwritten. Items sent withoutdatesget them from their text. metadata.page.dateFormat(short,long,numeric, oriso) sets how dates print.- Resumes no longer hold
cover-lettercustom sections. Any you send are saved as letters and removed from the resume.
DELETE /resumes/{id}andDELETE /cover-letters/{id}now move the document to Trash. Newdocumentsendpoints list, rename, tag, lock, trash, restore, and permanently delete (/documents/purge) resumes and letters together.- Creating or duplicating a resume no longer needs a
slug; one is generated from the name.GET /resumes/{resumeId}/slug-checkvalidates a slug (lowercase letters and numbers joined by single dashes) and suggests one. - Version entries have
kindandnameinstead oflabel. New endpoints get, create (named), rename, and delete resume versions, and letters have the same set under/cover-letters/{id}/versions. - Resume PDF downloads accept only the resume. The
cover-lettertarget is gone, and old signed links that ask for it return404. Download letters through/cover-letters.
POST /cover-letters/from-resume(copyEmbedded) is removed, since resumes no longer contain letters.- Letters gain
layout,recipientName,recipientCompany,letterDate,senderLinked, anddesignLinked, and adraftendpoint that drafts the letter body with the user’s AI provider.
- The
rejectedstage and thearchivedflag are gone. Usestatus: "closed"withclosedReason(not-selected,withdrew,accepted-other, orno-response). CSV import still readsrejectedandarchivedfrom older exports as closed. - Listing applications returns closed ones too;
includeArchivedis removed. Filter bystatusinstead. - New interview endpoints (
/applications/{id}/interviews), and new fields for the linked letter, what was sent (sentResumeVersionId,sentCoverLetterVersionId,sentCheckScore), andrequirements. - The account data export now includes applications.
POST /agent/threads/for-resume,POST /agent/threads/{id}/archive, andPOST /agent/actions/{id}/revertare removed. Start a conversation withPOST /agent/threads, passing eitherresumeIdorcoverLetterId.
MCP changes
- Removed:
copy_embedded_cover_letter. download_resume_pdfno longer takes atarget; it always returns the resume.delete_resumeanddelete_cover_letternow move the document to Trash.- Added:
add_application_interviewandupdate_application_interview. list_applicationsno longer takesincludeArchived.update_applicationno longer takesarchived, and acceptsclosedReasonwith theclosedstage.- Resume patch paths document the new
datesfield. The resume schema resource, like/schema.json, is now generated from the live schema.
Environment variables
Existing variables remain supported. New settings are optional unless your deployment uses the corresponding feature:AI_PROVIDER,AI_MODEL,AI_API_KEY, andAI_BASE_URLconfigure a server AI provider. Set provider, model, and key together; Ollama may omit the key.openai-compatiblealso requires a base URL. Configuring this shared provider disables personal AI providers. Leave these unset to continue using user-configured providers.WEB_ACCESS_PROVIDER,WEB_ACCESS_API_KEY, andWEB_ACCESS_API_URLconfigure server web access. Providers are Firecrawl, Tavily, and Exa. Set provider and key together; only Firecrawl accepts a custom API URL, which may be keyless.TRUSTED_PROXIESlists comma-separated proxy IP addresses or CIDRs. Behind nginx, Caddy, or Traefik, include the immediate proxy and any trusted intermediate hops. The proxy must append or replaceX-Forwarded-Forwith the actual client address. Leave this unset for direct connections. Never include public client networks: forwarded headers are trusted only through configured proxy addresses.FLAG_DISABLE_API_RATE_LIMITnow disables all API and authentication limits, including PDF exports and AI requests. Keep itfalseon public installations.REDIS_URLis optional for the assistant. Without Redis, replies can’t resume after a page reload, and Stop reaches only a reply running on the same server.ENCRYPTION_SECRETis still required for AI providers and the assistant. Vercel still requires Redis.- The web build now also produces
apps/web/dist-prerender(localized marketing pages). The Docker image includes it. If you build and deploy the server yourself, copy it next toapps/web/dist; without it, those pages render in the browser instead.
Schema check
After the migrations, the server compares the live database with the tables and columns it expects. If something is missing, for example after a partial restore, it logsDatabase schema verification failed and keeps running, which can lead to errors later.
Set STRICT_SCHEMA_CHECK=true to make startup fail instead. That’s a good choice for production: a failed start is easier to notice than errors in the middle of the day. To fix drift, restore a consistent backup or recreate the missing objects, then restart.
Roll back to v5
Restoring the backup you took before the upgrade is the safest way back, and the only one that undoes everything. Stop v6 first, restore the database (and files, if they changed), then start v5. If you must keep data written since the upgrade, you can undo the migrations that v5 can’t live with, by hand, before starting v5:- Stop every v6 instance.
-
Run the consolidated rollback script with
psql:The script puts letters back into the resumes they came from (one section per resume per run; run it again for a resume that had several), restoresapplication.archivedandresume_version.label, and turns closed applications back intorejectedor archived. These steps run in one transaction. - Start v5.
--restore with your backup file puts the originals back.
Related pages
- What’s new in v6: the user-facing changes.
- Self-hosting with Docker: updating and backing up a container installation.
- Custom styles: how converted styles look and how to edit them.