diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 2eae0132..6804ac2d 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -7,6 +7,7 @@
 - [What are editions?](editions/index.md)
   - [Creating a new project](editions/creating-a-new-project.md)
   - [Transitioning an existing project to a new edition](editions/transitioning-an-existing-project-to-a-new-edition.md)
+  - [Advanced migrations](editions/advanced-migrations.md)
 
 ## Rust 2015
 
diff --git a/src/editions/advanced-migrations.md b/src/editions/advanced-migrations.md
new file mode 100644
index 00000000..b804ae64
--- /dev/null
+++ b/src/editions/advanced-migrations.md
@@ -0,0 +1,210 @@
+# Advanced migration strategies
+
+## How migrations work
+
+[`cargo fix --edition`][`cargo fix`] works by running the equivalent of [`cargo check`] on your project with special [lints] enabled which will detect code that may not compile in the next edition.
+These lints include instructions on how to modify the code to make it compatible on both the current and the next edition.
+`cargo fix` applies these changes to the source code, and then runs `cargo check` again to verify that the fixes work.
+If the fixes fail, then it will back out the changes and display a warning.
+
+Changing the code to be simultaneously compatible with both the current and next edition makes it easier to incrementally migrate the code.
+If the automated migration does not completely succeed, or requires manual help, you can iterate while staying on the original edition before changing `Cargo.toml` to use the next edition.
+
+The lints that `cargo fix --edition` apply are part of a [lint group].
+For example, when migrating from 2018 to 2021, Cargo uses the `rust-2021-compatibility` group of lints to fix the code.
+Check the [Partial migration](#partial-migration-with-broken-code) section below for tips on using individual lints to help with migration.
+
+`cargo fix` may run `cargo check` multiple times.
+For example, after applying one set of fixes, this may trigger new warnings which require further fixes.
+Cargo repeats this until no new warnings are generated.
+
+## Migrating multiple configurations
+
+`cargo fix` can only work with a single configuration at a time.
+If you use [Cargo features] or [conditional compilation], then you may need to run `cargo fix` multiple times with different flags.
+
+For example, if you have code that uses `#[cfg]` attributes to include different code for different platforms, you may need to run `cargo fix` with the `--target` option to fix for different targets.
+This may require moving your code between machines if you don't have cross-compiling available.
+
+Similarly, if you have conditions on Cargo features, like `#[cfg(feature = "my-optional-thing")]`, it is recommended to use the `--all-features` flag to allow `cargo fix` to migrate all the code behind those feature gates.
+If you want to migrate feature code individually, you can use the `--features` flag to migrate one at a time.
+
+## Migrating a large project or workspace
+
+You can migrate a large project incrementally to make the process easier if you run into problems.
+
+In a [Cargo workspace], each package defines its own edition, so the process naturally involves migrating one package at a time.
+
+Within a [Cargo package], you can either migrate the entire package at once, or migrate individual [Cargo targets] one at a time.
+For example, if you have multiple binaries, tests, and examples, you can use specific target selection flags with `cargo fix --edition` to migrate just that one target.
+By default, `cargo fix` uses `--all-targets`.
+
+For even more advanced cases, you can specify the edition for each individual target in `Cargo.toml` like this:
+
+```toml
+[[bin]]
+name = "my-binary"
+edition = "2018"
+```
+
+This usually should not be required, but is an option if you have a lot of targets and are having difficulty migrating them all together.
+
+## Partial migration with broken code
+
+Sometimes the fixes suggested by the compiler may fail to work.
+When this happens, Cargo will report a warning indicating what happened and what the error was.
+However, by default it will automatically back out the changes it made.
+It can be helpful to keep the code in the broken state and manually resolve the issue.
+Some of the fixes may have been correct, and the broken fix maybe be *mostly* correct, but just need minor tweaking.
+
+In this situation, use the `--broken-code` option with `cargo fix` to tell Cargo not to back out the changes.
+Then, you can go manually inspect the error and investigate what is needed to fix it.
+
+Another option to incrementally migrate a project is to apply individual fixes separately, one at a time.
+You can do this by adding the individual lints as warnings, and then either running `cargo fix` (without the `--edition` flag) or using your editor or IDE to apply its suggestions if it supports "Quick Fixes".
+
+For example, the 2018 edition uses the [`keyword-idents`] lint to fix any conflicting keywords.
+You can add `#![warn(keyword_idents)]` to the top of each crate (like at the top of `src/lib.rs` or `src/main.rs`).
+Then, running `cargo fix` will apply just the suggestions for that lint.
+
+You can see the list of lints enabled for each edition in the [lint group] page, or run the `rustc -Whelp` command.
+
+## Migrating macros
+
+Some macros may require manual work to fix them for the next edition.
+For example, `cargo fix --edition` may not be able to automatically fix a macro that generates syntax that does not work in the next edition.
+
+This may be a problem for both [proc macros] and `macro_rules`-style macros.
+`macro_rules` macros can sometimes be automatically updated if the macro is used within the same crate, but there are several situations where it cannot.
+Proc macros in general cannot be automatically fixed at all.
+
+For example, if we migrate a crate containing this (contrived) macro `foo` from 2015 to 2018, `foo` would not be automatically fixed.
+
+```rust
+#[macro_export]
+macro_rules! foo {
+    () => {
+        let dyn = 1;
+        println!("it is {}", dyn);
+    };
+}
+```
+
+When this macro is defined in a 2015 crate, it can be used from a crate of any other edition due to macro hygiene (discussed below).
+In 2015, `dyn` is a normal identifier and can be used without restriction.
+
+However, in 2018, `dyn` is no longer a valid identifier.
+When using `cargo fix --edition` to migrate to 2018, Cargo won't display any warnings or errors at all.
+However, `foo` won't work when called from any crate.
+
+If you have macros, you are encouraged to make sure you have tests that fully cover the macro's syntax.
+You may also want to test the macros by importing and using them in crates from multiple editions, just to ensure it works correctly everywhere.
+If you run into issues, you'll need to read through the chapters of this guide to understand how the code can be changed to work across all editions.
+
+### Macro hygiene
+
+Macros use a system called "edition hygiene" where the tokens within a macro are marked with which edition they come from.
+This allows external macros to be called from crates of varying editions without needing to worry about which edition it is called from.
+
+Let's take a closer look at the example above that defines a `macro_rules` macro using `dyn` as an identifier.
+If that macro was defined in a crate using the 2015 edition, then that macro works fine, even if it were called from a 2018 crate where `dyn` is a keyword and that would normally be a syntax error.
+The `let dyn = 1;` tokens are marked as being from 2015, and the compiler will remember that wherever that code gets expanded.
+The parser looks at the edition of the tokens to know how to interpret it.
+
+The problem arises when changing the edition to 2018 in the crate where it is defined.
+Now, those tokens are tagged with the 2018 edition, and those will fail to parse.
+However, since we never called the macro from our crate, `cargo fix --edition` never had a chance to inspect the macro and fix it.
+
+<!-- TODO: hopefully someday, the reference will have chapters on how expansion works, and this can link there for actual details. -->
+
+## Documentation tests
+
+At this time, `cargo fix` is not able to update [documentation tests].
+After updating the edition in `Cargo.toml`, you should run `cargo test` to ensure everything still passes.
+If your documentation tests use syntax that is not supported in the new edition, you will need to update them manually.
+
+In rare cases, you can manually set the edition for each test.
+For example, you can use the [`edition2018` annotation][rustdoc-annotation] on the triple backticks to tell `rustdoc` which edition to use.
+
+## Generated code
+
+Another area where the automated fixes cannot apply is if you have a build script which generates Rust code at compile time (see [Code generation] for an example).
+In this situation, if you end up with code that doesn't work in the next edition, you will need to manually change the build script to generate code that is compatible.
+
+## Migrating non-Cargo projects
+
+If your project is not using Cargo as a build system, it may still be possible to make use of the automated lints to assist migrating to the next edition.
+You can enable the migration lints as described above by enabling the appropriate [lint group].
+For example, you can use the `#![warn(rust_2021_compatibility)]` attribute or the `-Wrust-2021-compatibility` or `--force-warns=rust-2021-compatibility` [CLI flag].
+
+The next step is to apply those lints to your code.
+There are several options here:
+
+* Manually read the warnings and apply the suggestions recommended by the compiler.
+* Use an editor or IDE that supports automatically applying suggestions.
+  For example, [Visual Studio Code] with the [Rust Analyzer extension] has the ability to use the "Quick Fix" links to automatically apply suggestions.
+  Many other editors and IDEs have similar functionality.
+* Write a migration tool using the [`rustfix`] library.
+  This is the library that Cargo uses internally to take the [JSON messages] from the compiler and modify the source code.
+  Check the [`examples` directory][rustfix-examples] for examples of how to use the library.
+
+## Writing idiomatic code in a new edition
+
+Editions are not only about new features and removing old ones.
+In any programming language, idioms change over time, and Rust is no exception.
+While old code will continue to compile, it might be written with different idioms today.
+
+For example, in Rust 2015, external crates must be listed with `extern crate` like this:
+
+```rust,ignore
+// src/lib.rs
+extern crate rand;
+```
+
+In Rust 2018, it is [no longer necessary](../rust-2018/path-changes.md#no-more-extern-crate) to include these items.
+
+`cargo fix` has the `--edition-idioms` option to automatically transition some of these idioms to the new syntax.
+
+> **Warning**: The current *"idiom lints"* are known to have some problems.
+> They may make incorrect suggestions which may fail to compile.
+> The current lints are:
+> * Edition 2018:
+>     * [`unused-extern-crates`]
+>     * [`explicit-outlives-requirements`]
+> * Edition 2021 does not have any idiom lints.
+>
+> The following instructions are recommended only for the intrepid who are willing to work through a few compiler/Cargo bugs!
+> If you run into problems, you can try the `--broken-code` option [described above](#partial-migration-with-broken-code) to make as much progress as possible, and then resolve the remaining issues manually.
+
+With that out of the way, we can instruct Cargo to fix our code snippet with:
+
+```console
+cargo fix --edition-idioms
+```
+
+Afterwards, the line with `extern crate rand;` in `src/lib.rs` will be removed.
+
+We're now more idiomatic, and we didn't have to fix our code manually!
+
+[`cargo check`]: ../../cargo/commands/cargo-check.html
+[`cargo fix`]: ../../cargo/commands/cargo-fix.html
+[`explicit-outlives-requirements`]:  ../../rustc/lints/listing/allowed-by-default.html#explicit-outlives-requirements
+[`keyword-idents`]: ../../rustc/lints/listing/allowed-by-default.html#keyword-idents
+[`rustfix`]: https://github.com/rust-lang/rustfix
+[`unused-extern-crates`]: ../../rustc/lints/listing/allowed-by-default.html#unused-extern-crates
+[Cargo features]: ../../cargo/reference/features.html
+[Cargo package]: ../../cargo/reference/manifest.html#the-package-section
+[Cargo targets]: ../../cargo/reference/cargo-targets.html
+[Cargo workspace]: ../../cargo/reference/workspaces.html
+[CLI flag]: ../../rustc/lints/levels.html#via-compiler-flag
+[Code generation]: ../../cargo/reference/build-script-examples.html#code-generation
+[conditional compilation]: ../../reference/conditional-compilation.html
+[documentation tests]: ../../rustdoc/documentation-tests.html
+[JSON messages]: ../../rustc/json.html
+[lint group]: ../../rustc/lints/groups.html
+[lints]: ../../rustc/lints/index.html
+[proc macros]: ../../reference/procedural-macros.html
+[Rust Analyzer extension]: https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer
+[rustdoc-annotation]: ../../rustdoc/documentation-tests.html#attributes
+[rustfix-examples]: https://github.com/rust-lang/rustfix/tree/master/examples
+[Visual Studio Code]: https://code.visualstudio.com/
diff --git a/src/editions/transitioning-an-existing-project-to-a-new-edition.md b/src/editions/transitioning-an-existing-project-to-a-new-edition.md
index beb23604..6732837e 100644
--- a/src/editions/transitioning-an-existing-project-to-a-new-edition.md
+++ b/src/editions/transitioning-an-existing-project-to-a-new-edition.md
@@ -1,37 +1,54 @@
 # Transitioning an existing project to a new edition
 
-New editions might change the way you write Rust – they add new syntax,
-language, and library features, and also remove features. For example, `try`,
-`async`, and `await` are keywords in Rust 2018, but not Rust 2015. If you
-have a project that's using Rust 2015, and you'd like to use Rust 2018 for it
-instead, there's a few steps that you need to take.
+Rust includes tooling to automatically transition a project from one edition to the next.
+It will update your source code so that it is compatible with the next edition.
+Briefly, the steps to update to the next edition are:
+
+1. Run `cargo fix --edition`
+2. Edit `Cargo.toml` and set the `edition` field to the next edition, for example `edition = "2021"`
+3. Run `cargo build` or `cargo test` to verify the fixes worked.
+
+<!-- remove this when 2021 is stabilized -->
+> If you are migrating from 2018 to 2021, the steps are slightly different because 2021 is not yet stabilized, and is only available on the [nightly channel].
+> The steps to follow are:
+>
+> 1. Install the most recent nightly: `rustup update nightly`.
+> 2. Run `cargo +nightly fix --edition`.
+> 3. Edit `Cargo.toml` and place `cargo-features = ["edition2021"]` at the top (above `[package]`), and change the edition field to say `edition = "2021"`.
+> 4. Run `cargo +nightly check` to verify it now works in the new edition.
+
+The following sections dig into the details of these steps, and some of the issues you may encounter along the way.
 
 > It's our intention that the migration to new editions is as smooth an
 > experience as possible. If it's difficult for you to upgrade to the latest edition,
 > we consider that a bug. If you run into problems with this process, please
-> [file a bug](https://github.com/rust-lang/rust/issues/new). Thank you!
+> [file a bug](https://github.com/rust-lang/rust/issues/new/choose). Thank you!
+
+## Starting the migration
+
+As an example, let's take a look at transitioning from the 2015 edition to the 2018 edition.
+The steps are essentially the same when transitioning to other editions like 2021.
 
-Here's an example. Imagine we have a crate that has this code in
-`src/lib.rs`:
+Imagine we have a crate that has this code in `src/lib.rs`:
 
 ```rust
 trait Foo {
-    fn foo(&self, Box<Foo>);
+    fn foo(&self, i32);
 }
 ```
 
-This code uses an anonymous parameter, that `Box<Foo>`. This is [not
+This code uses an anonymous parameter, that `i32`. This is [not
 supported in Rust 2018](../rust-2018/trait-system/no-anon-params.md), and
 so this would fail to compile. Let's get this code up to date!
 
 ## Updating your code to be compatible with the new edition
 
-Your code may or may not use features that are incompatible with the new
-edition. In order to help transition to Rust 2018, we've included a new
-subcommand with Cargo. To start, let's run it:
+Your code may or may not use features that are incompatible with the new edition.
+In order to help transition to the next edition, Cargo includes the [`cargo fix`] subcommand to automatically update your source code.
+To start, let's run it:
 
 ```console
-> cargo fix --edition
+cargo fix --edition
 ```
 
 This will check your code, and automatically fix any issues that it can.
@@ -39,106 +56,43 @@ Let's look at `src/lib.rs` again:
 
 ```rust
 trait Foo {
-    fn foo(&self, _: Box<Foo>);
+    fn foo(&self, _: i32);
 }
 ```
 
-It's re-written our code to introduce a parameter name for that trait object.
+It's re-written our code to introduce a parameter name for that `i32` value.
 In this case, since it had no name, `cargo fix` will replace it with `_`,
 which is conventional for unused variables.
 
 `cargo fix` can't always fix your code automatically.
 If `cargo fix` can't fix something, it will print the warning that it cannot fix
-to the console. If you see one of these warnings, you'll have to update your code
-manually. See the corresponding section of this guide for help, and if you have
-problems, please seek help at the [user's forums](https://users.rust-lang.org/).
-
-Keep running `cargo fix --edition` until you have no more warnings.
-
-Congrats! Your code is now valid in both Rust 2015 and Rust 2018!
+to the console. If you see one of these warnings, you'll have to update your code manually.
+See the [Advanced migration strategies] chapter for more on working with the migration process, and read the chapters in this guide which explain which changes are needed.
+If you have problems, please seek help at the [user's forums](https://users.rust-lang.org/).
 
 ## Enabling the new edition to use new features
 
 In order to use some new features, you must explicitly opt in to the new
-edition. Once you're ready to commit, change your `Cargo.toml` to add the new
+edition. Once you're ready to continue, change your `Cargo.toml` to add the new
 `edition` key/value pair. For example:
 
 ```toml
 [package]
 name = "foo"
 version = "0.1.0"
-authors = ["Your Name <you@example.com>"]
 edition = "2018"
 ```
 
 If there's no `edition` key, Cargo will default to Rust 2015. But in this case,
-we've chosen `2018`, and so our code is compiling with Rust 2018!
-
-## Writing idiomatic code in a new edition
-
-Editions are not only about new features and removing old ones. In any programming
-language, idioms change over time, and Rust is no exception. While old code
-will continue to compile, it might be written with different idioms today.
-
-Our sample code contains an outdated idiom. Here it is again:
-
-```rust
-trait Foo {
-    fn foo(&self, _: Box<Foo>);
-}
-```
-
-In Rust 2018, it's considered idiomatic to use the [`dyn`
-keyword](../rust-2018/trait-system/dyn-trait-for-trait-objects.md) for
-trait objects.
-
-Eventually, we want `cargo fix` to fix all these idioms automatically in the same
-manner we did for upgrading to the 2018 edition. **Currently,
-though, the *"idiom lints"* are not ready for widespread automatic fixing.** The
-compiler isn't making `cargo fix`-compatible suggestions in many cases right
-now, and it is making incorrect suggestions in others. Enabling the idiom lints,
-even with `cargo fix`, is likely to leave your crate either broken or with many
-warnings still remaining.
+we've chosen `2018`, and so our code will compile with Rust 2018!
 
-We have plans to make these idiom migrations a seamless part of the Rust 2018
-experience, but we're not there yet. As a result the following instructions are
-recommended only for the intrepid who are willing to work through a few
-compiler/Cargo bugs!
+The next step is to test your project on the new edition.
+Run your project tests to verify that everything still works, such as running [`cargo test`].
+If new warnings are issued, you may want to consider running `cargo fix` again (without the `--edition` flag) to apply any suggestions given by the compiler.
 
-With that out of the way, we can instruct Cargo to fix our code snippet with:
-
-```console
-$ cargo fix --edition-idioms
-```
-
-Afterwards, `src/lib.rs` looks like this:
-
-```rust
-trait Foo {
-    fn foo(&self, _: Box<dyn Foo>);
-}
-```
-
-We're now more idiomatic, and we didn't have to fix our code manually!
-
-Note that `cargo fix` may still not be able to automatically update our code.
-If `cargo fix` can't fix something, it will print a warning to the console, and
-you'll have to fix it manually.
-
-As mentioned before, there are known bugs around the idiom lints which
-means they're not all ready for prime time yet. You may get a scary-looking
-warning to report a bug to Cargo, which happens whenever a fix proposed by
-`rustc` actually caused code to stop compiling by accident. If you'd like `cargo
-fix` to make as much progress as possible, even if it causes code to stop
-compiling, you can execute:
-
-```console
-$ cargo fix --edition-idioms --broken-code
-```
-
-This will instruct `cargo fix` to apply automatic suggestions regardless of
-whether they work or not. Like usual, you'll see the compilation result after
-all fixes are applied. If you notice anything wrong or unusual, please feel free
-to report an issue to Cargo and we'll help prioritize and fix it.
+Congrats! Your code is now valid in both Rust 2015 and Rust 2018!
 
-Enjoy the new edition!
+[`cargo fix`]: ../../cargo/commands/cargo-fix.html
+[`cargo test`]: ../../cargo/commands/cargo-test.html
+[Advanced migration strategies]: advanced-migrations.md
+[nightly channel]: ../../book/appendix-07-nightly-rust.html