workerd uses a compatibility date system to ensure that updating the runtime never breaks existing workers. Every worker must specify a compatibility date, and workerd will emulate the API as it existed on that date.
Why compatibility dates?
Runtime environments typically face a dilemma when fixing bugs or changing behavior:
- Don’t fix the bug: Users depend on broken behavior, so changing it could break their applications
- Fix the bug: New applications work correctly, but existing applications break
Compatibility dates solve this by allowing both:
- New workers get the fixed behavior
- Old workers continue using the old behavior
- Each worker can opt in to fixes on their own schedule
How compatibility dates work
Every worker specifies a date
In your worker configuration, specify a compatibilityDate:
const myWorker :Workerd.Worker = (
serviceWorkerScript = embed "worker.js",
compatibilityDate = "2024-01-15"
);
This tells workerd: “Make this worker behave as if it’s running on the version of workerd that was current on January 15, 2024.”
Breaking changes are dated
When workerd needs to change behavior, the change is associated with a date:
formDataParserSupportsFiles @0 :Bool
$compatEnableFlag("formdata_parser_supports_files")
$compatEnableDate("2021-11-03")
$compatDisableFlag("formdata_parser_converts_files_to_strings");
This means:
- Workers with
compatibilityDate >= "2021-11-03": Files in FormData work correctly
- Workers with
compatibilityDate < "2021-11-03": Files are converted to strings (old behavior)
workerd versions are dates
workerd’s version number is simply a date, like 20240115. This corresponds to the maximum compatibility date that version supports.
You can run a worker with compatibilityDate = "2023-01-01" on workerd 20240115, and it will emulate the 2023 behavior.
Compatibility flags
Compatibility dates work by controlling a set of compatibility flags defined in src/workerd/io/compatibility-date.capnp.
Automatic flags
Flags are automatically enabled based on your compatibility date:
specCompliantUrl @10 :Bool
$compatEnableFlag("url_standard")
$compatEnableDate("2022-10-31")
$compatDisableFlag("url_original");
With compatibilityDate = "2023-01-01":
- This flag is enabled (date is after 2022-10-31)
- Your worker gets spec-compliant URL parsing
With compatibilityDate = "2022-06-01":
- This flag is disabled (date is before 2022-10-31)
- Your worker gets the original URL parsing behavior
Manual flags
You can explicitly enable or disable flags:
const myWorker :Workerd.Worker = (
serviceWorkerScript = embed "worker.js",
compatibilityDate = "2022-06-01",
compatibilityFlags = [
"url_standard" # Opt in to new URL behavior early
]
);
Flag naming
Each flag has:
- Enable flag: Name to enable the new behavior (e.g.,
"url_standard")
- Disable flag: Name to keep old behavior (e.g.,
"url_original")
- Enable date: Date when the flag becomes default
The disable flag allows opting out:
compatibilityDate = "2023-01-01",
compatibilityFlags = [
"url_original" # Keep old behavior despite new date
]
Example flags from workerd
formDataParserSupportsFiles @0 :Bool
$compatEnableFlag("formdata_parser_supports_files")
$compatEnableDate("2021-11-03")
$compatDisableFlag("formdata_parser_converts_files_to_strings");
The original implementation incorrectly turned files into strings. This flag fixes it for workers with dates >= 2021-11-03.
URL parsing
specCompliantUrl @10 :Bool
$compatEnableFlag("url_standard")
$compatEnableDate("2022-10-31")
$compatDisableFlag("url_original");
The original URL implementation wasn’t spec-compliant. This flag enables the standard-compliant implementation.
WebSocket compression
webSocketCompression @20 :Bool
$compatEnableFlag("web_socket_compression")
$compatEnableDate("2023-08-15")
$compatDisableFlag("no_web_socket_compression");
WebSocket compression was added. Workers with dates before 2023-08-15 don’t have compression, ensuring they behave identically.
Node.js compatibility
nodeJsCompat @21 :Bool
$compatEnableFlag("nodejs_compat")
$compatDisableFlag("no_nodejs_compat");
Some flags don’t have enable dates - they must be explicitly opted into. Node.js compatibility is opt-in because it adds significant APIs and changes behavior.
Async API exception handling
captureThrowsAsRejections @12 :Bool
$compatEnableFlag("capture_async_api_throws")
$compatEnableDate("2022-10-31")
$compatDisableFlag("do_not_capture_async_api_throws");
Worker APIs that return promises should never throw synchronously. This flag fixes them to return rejections instead, matching Web Platform specs.
Choosing a compatibility date
For new workers
Use a recent date, typically the current date:
compatibilityDate = "2024-01-15"
This ensures you get the latest bug fixes and behavior improvements.
For existing workers
When updating a worker’s compatibility date:
- Read the changelog: Review changes between your old date and the new date
- Test thoroughly: Test your worker with the new date in development
- Update incrementally: Consider updating in steps rather than jumping to the latest date
- Use flags if needed: If a change breaks something, use a disable flag temporarily
Compatibility date requirements
workerd versions only support compatibility dates within a certain range. Very old dates may eventually stop being supported as maintaining compatibility becomes infeasible.
A workerd version like 20240115 supports:
- Maximum date:
2024-01-15 (the version date)
- Minimum date: Typically several years back (check workerd documentation)
How to update compatibility dates
Step 1: Check the changelog
Review changes between your current date and the target date. On Cloudflare Workers, this is at:
https://developers.cloudflare.com/workers/platform/compatibility-dates/
For workerd, check the compatibility-date.capnp file.
Step 2: Test locally
Update your config and test:
const myWorker :Workerd.Worker = (
serviceWorkerScript = embed "worker.js",
compatibilityDate = "2024-01-15" # Updated from "2023-01-01"
);
Run your tests:
workerd serve config.capnp
Step 3: Test in staging
Deploy to a staging environment:
workerd serve config.capnp --socket-addr http=staging:8080
Run integration tests and manual verification.
Step 4: Deploy to production
Once confident, deploy the updated configuration to production.
Rollback if needed
If issues occur, simply revert the configuration change:
compatibilityDate = "2023-01-01" # Reverted
The old behavior is instantly restored.
Compatibility flags for gradual migration
If a breaking change affects your worker, use flags to migrate gradually:
Option 1: Keep old behavior temporarily
compatibilityDate = "2024-01-15",
compatibilityFlags = [
"url_original" # Keep old URL behavior while fixing code
]
Fix your code, then remove the flag:
compatibilityDate = "2024-01-15"
# Flag removed - now using new behavior
Option 2: Opt in to new behavior early
compatibilityDate = "2022-06-01",
compatibilityFlags = [
"url_standard" # Opt in before the enable date
]
Test the new behavior before updating your date.
Experimental flags
Some flags are marked experimental:
workerdExperimental @24 :Bool
$compatEnableFlag("experimental")
$experimental;
Experimental flags:
- Are subject to change or removal
- Are not covered by compatibility guarantees
- Require the
--experimental CLI flag
- Should not be used in production
Experimental features may change or be removed without warning. Use them only for testing and development.
Best practices
Keep compatibility dates current
Regularly update your compatibility dates to get bug fixes and improvements:
# Review and update quarterly
compatibilityDate = "2024-01-15"
Document your compatibility date
Include a comment explaining why you chose this date:
const myWorker :Workerd.Worker = (
serviceWorkerScript = embed "worker.js",
# Set to 2024-01-15 during initial development
# Last reviewed: 2024-01-15
compatibilityDate = "2024-01-15"
);
Test before updating
Always test compatibility date changes before deploying to production:
- Update date in development
- Run automated tests
- Perform manual testing
- Deploy to staging
- Monitor staging carefully
- Deploy to production
Use feature detection
When possible, use feature detection rather than depending on specific dates:
export default {
async fetch(request) {
// Good: Feature detection
if ('getSetCookie' in request.headers) {
const cookies = request.headers.getSetCookie();
}
// Avoid: Assuming feature based on date
const cookies = request.headers.getSetCookie(); // Might not exist
}
}
Compatibility dates and deployments
Different deployments can use different compatibility dates:
# Development: use latest features
const devWorker :Workerd.Worker = (
inherit = "baseWorker",
compatibilityDate = "2024-01-15"
);
# Production: use stable date
const prodWorker :Workerd.Worker = (
inherit = "baseWorker",
compatibilityDate = "2023-06-01"
);
This allows testing new behavior in development before enabling it in production.