Zip-managed TypeDock installations can update Core from
Admin → System Update beginning with 1.0.0-rc6. Source checkouts and
containers continue to use their normal deployment workflow. The same page
retains a machine-readable context and agent prompt for hosts where PHP cannot
replace the installed files.
This keeps the update path portable across shared hosting, VPS, Docker,
Git checkouts, split public_html layouts, and sites with custom themes
or plugins.
The Short Version
- Open
/admin/system/update. - Click Check now.
- Click Download and verify.
- Review ownership warnings for themes and plugins.
- Confirm the maintenance notice and apply the staged release.
TypeDock verifies the release checksum and minisign signature against its pinned primary/recovery keyring before staging. This lets a recovery-signed release rotate a lost or compromised normal release key. Official releases also include a Sigstore keyless bundle with Rekor proof for independent verification; the PHP updater deliberately continues to require minisign and does not treat Sigstore as an alternate acceptance path. Applying creates database and file backups, enters maintenance mode, swaps only manifest-owned paths, runs migrations, republishes assets, and verifies the installed file hashes. A caught failure triggers automatic rollback.
If you use a coding agent, give it the prompt from /admin/system/update
or:
php cli/upgrade.php --agent-prompt
What TypeDock Helps With
TypeDock Core provides:
- A package manifest:
typedock-package.json - Signed release download and safe zip staging
- Installation mode detection:
zip,source, orcontainer - Theme/plugin ownership checks
- Database and replaced-file backups
- Maintenance mode, migration, asset publishing, and automatic rollback
- Agent handoff context:
php cli/upgrade.php --agent-context
php cli/upgrade.php --agent-prompt
In-place apply is deliberately limited to zip-managed installations. Git/source and container installs show the preflight and agent context but do not offer an apply button.
Files to Preserve
Never overwrite these during a Core upgrade:
| Path | Why |
|---|---|
config.php |
Site secrets and environment settings |
storage/ |
Cache, logs, sessions, backups, SQLite DB |
public/uploads/ |
Uploaded media |
themes/<user-owned>/ |
Custom or third-party themes |
plugins/<user-owned>/ |
Custom or third-party plugins |
.env |
Legacy compatibility, if present |
public/themes/ and public/plugins/ are generated publish output.
They can be regenerated with:
php cli/assets-publish.php
Files Usually Replaced by Core
The release package manifest lists the Core-managed paths. Typical managed paths include:
vendor/
src/
migrations/
cli/
admin/
public/admin/dist/
public/admin/assets/
public/index.php
public/install.php
config/
composer.json
composer.lock
LICENSE
README.md
Bundled themes and plugins are handled separately because users often customize them by mistake.
Cloud Storage is distributed as a separate official plugin beginning with
TypeDock 1.0. Core release packages do not add or replace
plugins/cloud-storage/. If it is already installed, preserve that directory
during upgrades. New installations can download the
typedock-cloud-storage-*.zip asset from the matching GitHub release and
upload it from Settings -> Modules, or copy the extracted directory over
FTP when the host's upload limit is too small.
Theme and Plugin Ownership
The updater preflight classifies theme and plugin directories:
| Status | Meaning | What to do |
|---|---|---|
clean |
Bundled by TypeDock and unchanged | Safe to replace from the release |
modified |
Bundled by TypeDock but locally changed | Back it up, inspect the diff, then decide |
managed-untracked |
Bundled by TypeDock, but this install lacks package hashes | Treat as modified; back it up first |
removed-bundled |
Bundled by the old release but now distributed separately | Preserve it as an installed extension |
user-owned |
Not owned by TypeDock Core | Do not overwrite |
collision |
A user-owned slug conflicts with a new bundled slug | Stop and resolve manually |
Best practice: do not edit bundled themes like default or kinari
directly. Copy them to a new slug and customize the copy.
Shared Hosting Upgrade
Use the Admin flow above when the site was installed from the shared-hosting
zip at 1.0.0-rc6 or newer. The manual fallback is:
- Put the site in maintenance mode by creating
storage/.maintenanceif you are doing the replacement manually. - Back up the database from the hosting panel. For SQLite, copy the
configured SQLite file under
storage/. - Back up the current TypeDock files, especially bundled themes/plugins
reported as
modifiedormanaged-untracked. - Upload the new release package to a temporary folder.
- Replace only Core-managed paths.
- Preserve
config.php,storage/,public/uploads/, and user-ownedthemes//plugins/. - Run:
php cli/migrate.php
php cli/assets-publish.php
php cli/cache-clear.php
If you do not have shell access, use your hosting panel's PHP command runner if available. Otherwise, upload the files first and visit the admin area to confirm whether migrations are required.
- Remove
storage/.maintenance. - Check
/,/admin/login,/sitemap.xml, and/feed.
Git or Composer Checkout Upgrade
If the preflight says the install mode is source, update it through
your normal source-control workflow:
git fetch --tags
git checkout <target-tag>
composer install --no-dev --optimize-autoloader
php cli/migrate.php
php cli/assets-publish.php
php cli/cache-clear.php
Review local theme/plugin changes before switching tags. A coding agent
can use php cli/upgrade.php --agent-context to understand which
directories are Core-owned and which are user-owned.
Docker Upgrade
If the preflight says the install mode is container, do not replace
files inside the running container. Build or pull a new image, recreate
the container, then run migrations against the persistent database.
Typical flow:
docker compose pull
docker compose up -d
docker compose exec app php cli/migrate.php
docker compose exec app php cli/assets-publish.php
Use the exact service name from your deployment.
Using a Coding Agent
TypeDock is designed to make upgrade work legible to a coding agent. Give the agent:
php cli/upgrade.php --agent-prompt
or copy the prompt from /admin/system/update.
The agent must:
- Verify the release artifact signature/checksum.
- Back up files and the database before changing anything.
- Preserve user-owned themes and plugins.
- Back up and explain any modified bundled theme/plugin before replacing it.
- Run migrations and asset publishing.
- Smoke test the site.
- Restore from backup if any step fails.
The agent should report exactly what changed, what was preserved, and where backups were written.
Breaking Changes
After 1.0.0-rc6: non-ASCII slugs
Page and post slugs now accept letters from any script, so a Japanese, Greek or
Cyrillic site can use its own words in a URL. A title in one of those scripts
used to be stripped to nothing and fall back to a timestamp
(post-20260728151311), which is what made migrating a non-English WordPress
site lose every inbound link.
Slugs are stored decoded (お知らせ, not %E3%81%8A%E7%9F%A5%E3%82%89%E3%81%9B)
and percent-encoded only when written into a URL. This is a change for category
and tag slugs, which were previously stored encoded — a form no request could
match, since the router hands controllers the decoded value, so every non-ASCII
category and tag archive returned 404. A migration rewrites those rows on
upgrade; ASCII slugs are untouched, and a row whose decoded form is already
taken is left alone rather than failing the upgrade.
If anything on your side reads categories.slug or tags.slug directly and
expects the escaped form, decode on output instead. Themes using $post->url
or $post->slug need no change: url is encoded, slug is the stored value.
What a slug may contain, and why:
- Base letters and digits from any script, plus
-and/. Characters with meaning in a URL —?,#,%,., whitespace — stay out, so a stored slug never has to be parsed to be understood. - No combining marks. Nothing normalises Unicode here, so allowing them
would let
がexist twice — precomposed (U+304C) and decomposed (U+304B U+3099) — as two rows that look identical and answer to different URLs. Refusing the decomposed form is what makes a byte comparison a correct comparison. Scripts whose marks are structural rather than optional (Devanagari, Thai) therefore cannot be used in a slug yet. - No invisible letters. The Hangul fillers (U+115F, U+1160, U+3164, U+FFA0) are ordinary letters as far as Unicode categories are concerned, but render as nothing, so a slug built from them would be invisible in an admin list, a sitemap and a URL bar alike.
- Modifier letters such as U+30FC (
コーヒー) and U+3005 (人々) are allowed: they are ordinary Japanese.
A request whose path decodes to something that cannot be a slug — invalid UTF-8, a NUL or control character, or more than 1000 bytes — is answered with 404 before any database lookup, rather than being passed to a UTF-8 column that would reject it.
After 1.0.0-rc6: sitemap URLs
The sitemap index has always listed /sitemap-pages.xml, /sitemap-posts.xml
and /sitemap-categories.xml, but only /sitemap.xml had a route, so those
three 404'd. They are now served, and the undocumented ?type=pages form that
stood in for them has been removed. If anything on your side fetched
/sitemap.xml?type=posts, point it at /sitemap-posts.xml.
After 1.0.0-rc6: home page canonical
A static page assigned to the site root (Settings → General, home mode "page")
previously advertised its own slug — canonical, og:url and the JSON-LD
url all read /home while the page was served from /. All three now point
at the site root, matching what an archive home page has always emitted. The
page keeps its own title, description and OG image.
If you worked around this in a theme by overriding canonical, OG/Twitter and JSON-LD for the home page, that override can now be removed.
SeoService::generateJsonLd() takes the canonical URL as a third argument
instead of rebuilding one from the page slug. Plugins calling it directly need
to pass the URL.
Post URLs in the posts sitemap now include the configured posts archive slug
(/blog/example-post, or /articles/example-post if you changed it under
Settings → General). Previously they were emitted as /example-post, which
does not resolve. Resubmit your sitemap after upgrading.
Rollback
If a caught error occurs after live replacement starts, TypeDock automatically
restores the recorded database and files. If the PHP request itself is killed
mid-swap, revisit /admin/system/update with the maintenance bypass link from
the interrupted session and click Restore previous release.
Manual rollback remains environment-specific:
- Restore the database backup.
- Restore the previous Core-managed files.
- Restore any bundled theme/plugin backup that was overwritten.
- Run
php cli/assets-publish.php. - Run
php cli/cache-clear.php. - Remove
storage/.maintenance.
After rollback, check /, /admin/login, /sitemap.xml, and /feed.
Troubleshooting
The preflight reports managed-untracked
The install does not have file hashes for bundled themes/plugins, usually
because it predates typedock-package.json hashes or was installed from
a development archive. Treat these directories as locally modified:
back them up before replacing them.
The preflight reports an ownership collision
A local user-owned theme/plugin uses a slug that a new TypeDock release wants to claim as bundled. Stop the upgrade and rename or remove the local extension after reviewing it.
The admin looks unstyled after upgrade
Run:
php cli/assets-publish.php
php cli/cache-clear.php
Then clear any host-level cache or CDN cache.
Everyone was signed out of the admin after upgrading
Expected once, on the release that renamed the cookies. The admin login
cookie is now typedock_auth (was cms_session) and the PHP session cookie
is typedock_session (was PHPSESSID), so both share a typedock_ prefix
and a CDN can bypass the cache for signed-in visitors with one rule. Sign in
again; nothing else is affected. Override the names with AUTH_COOKIE_NAME
and SESSION_NAME in config.php if you need the old ones back.
Migrations fail
The admin updater keeps maintenance mode active while migrating and attempts rollback immediately. SQLite rollback is an exact file restore. MySQL, PostgreSQL, and libSQL use a portable row snapshot; schema changes are forward-only, so a migration that destructively removes old schema may still require the hosting provider's database backup.
Source: https://github.com/typedock/core/blob/main/docs/upgrade.md