This article explains how to move pages and images from one Umbraco site to another with Umbraco’s built-in package feature. The pages keep their keys, their links to each other, and their images. You do not need to copy the database or add a sync tool such as Umbraco Deploy or uSync.
In this article, the source is the site the pages come from, for example staging. The target is the site they go to, for example production.
The steps were tested on two Umbraco 17.6.2 sites. To try them on two throwaway local sites first, see the demo.
A package must carry pages and images only. The target must already have the Document Types the pages use, with the same properties.
Leave Document Types, Templates, Data Types, stylesheets, and scripts out of the package. If the target already has one of those items, the install updates the target’s copy. Leaving them out keeps the target safe.
When the package carries pages and images only:
package_needs.py.
The script needs no extra libraries.A package takes one content node and, optionally, all the nodes below it. You cannot pick individual pages, so place everything you want to send under one node, such as a folder page.
The examples in this article send the following page:
For more information, see Creating a Package Schema in the Backoffice in the Umbraco documentation.
Packages section.Created in the top-right corner of the
screen.Create package.Site Pages 1.Content, select the top node and include its
child nodes.Media, select the images the pages use.Document Types,
Media Types, Data Types,
Templates, Stylesheets, Scripts,
Partial Views, Languages, and
Dictionary.Create, then Download.The download is a package.zip file. The ZIP file
contains package.xml and the image files.
You can also create the package with two calls to the Management
API. Both calls need an API user’s access token in the
Authorization: Bearer header. Send the body of the first
call as JSON. The examples leave out the headers.
Create the package:
POST /umbraco/management/api/v1/package/created
{
"name": "Site Pages 1",
"contentNodeId": "<key of the top node>", "contentLoadChildNodes": true,
"mediaIds": ["<key of each image>"], "mediaLoadChildNodes": false,
"documentTypes": [], "mediaTypes": [], "dataTypes": [], "templates": [], "partialViews": [],
"stylesheets": [], "scripts": [], "languages": [], "dictionaryItems": []
}Download the package, using the ID from the Location
header of the first response:
GET /umbraco/management/api/v1/package/created/<id>/downloadRun package_needs.py on the downloaded file:
python3 package_needs.py package.zipThe output looks like this:
Document Types the target must already have:
jobAid: body
jobAidFolder: (no properties)
Media Types the target must already have:
Image: umbracoBytes, umbracoExtension, umbracoFile, umbracoHeight, umbracoWidth
Carries only pages and images: yes
The script reads package.xml and lists:
The last line must say yes. If the last line says
NO, also DocumentTypes or similar, the package carries more
than pages and images. Clear those fields on the source and create the
package again.
On the target, navigate to Settings >
Document Types. For each Document Type the script listed,
check that:
Then check each page’s template. In Settings >
Templates, check that:
The script cannot check property editors, because
package.xml stores values, not editors. Compare the
property editors on the two sites yourself.
The install matches templates by alias, and only uses a template the Document Type allows. Otherwise, the page gets the Document Type’s default template (PackageDataInstallation.cs). The page uses the target’s own template file, not the source’s.
Do not skip this check. A missing property does not stop the install, and the log does not mention the missing property. The content for that property is dropped without a warning. For other mismatches, see Troubleshooting.
The target installs the package at startup through an automatic
package migration. You add two things to the target project once.
For later batches, you replace only the package.zip
file.
Add a class that inherits from
AutomaticPackageMigrationPlan:
// ContentImport/ContentImportPackage.cs
using Umbraco.Cms.Infrastructure.Packaging;
namespace MySite.ContentImport;
public class ContentImportPackage : AutomaticPackageMigrationPlan
{
public ContentImportPackage() : base("Site Pages") { }
}Save the downloaded file as
ContentImport/package.zip, and add it to the
.csproj file as an embedded resource:
<ItemGroup>
<EmbeddedResource Include="ContentImport/package.zip" LogicalName="MySite.ContentImport.package.zip" />
</ItemGroup>The LogicalName must be the class’s namespace followed
by .package.zip or .package.xml. Umbraco looks
for the package by that name (PackageMigrationResource.cs).
The name passed to base, Site Pages in the
example, names the migration. It does not need to match the package name
you entered on the source (AutomaticPackageMigrationPlan.cs).
Build, deploy, and restart the target. The install runs on its own at startup.
Check the log for these three lines:
Starting package migration for Site Pages
Package migration executed.
Package migration completed for Site Pages
If the lines are missing, see Troubleshooting.
Startup installs are on by default. The
Umbraco:CMS:Unattended:PackageMigrationsUnattended setting
must not be false.
In the target’s backoffice, open the Content section and
check that:
Publish the pages on the target when you are ready. Umbraco builds each page’s URL from its name.
Site Pages 2.ContentImport/package.zip on the target with
the new file. Keep the same file name, class, and name passed to
base.The install runs again because the contents of the file changed. A restart with an unchanged file does not run the install (AutomaticPackageMigrationPlan.cs).
You can send the same folder again with new pages added. The install skips pages the target already has, so only the new pages arrive. Nothing is duplicated.
A package cannot update a page the target already has. The install skips any page whose key already exists on the target (ImportPackageBuilderExpression.cs). To send a correction, remove the target’s copy first:
Empty the recycle bin before you send the page. A page in the recycle bin still has its key, so the install skips the page.
For a small change, you can make the correction on the target instead. From then on, treat the target as the main copy. To avoid corrections, send a page only when it is final.
| Problem | Cause | Solution |
|---|---|---|
| The target does not start after the deploy. | A Document Type the pages use is missing on the target. | Remove package.zip and redeploy. Add the Document Type,
then try again. |
| The log has no package migration lines. | The file has not changed since the last install, or the
LogicalName does not match the class’s namespace. |
Check that the file changed. Check that the name is
<namespace>.package.zip. |
| The pages arrive blank, or without one property. | A property is missing on the target’s Document Type. | Add the property. Delete the blank pages on the target and send them again. |
Text displays as raw code, like
{"markup":"<p>…. |
The property uses a different editor on the target. | Change the editor to match. Then fix the pages on the target, or delete them and send them again. |
| A correction on the source did not arrive. | The target already had the page. A package does not update existing pages. | See Sending a Correction. |
| A page deleted on the target did not return with the next package. | The page is still in the target’s recycle bin. | Empty the recycle bin, then send a new package. |
| A page arrives with the wrong template. | The target has no template with that alias, or the Document Type does not allow the template. | Add or allow the template. Then set the template on the page. |
| Every page returns a 404 error after a deploy, even published pages. | The site rebuilt an empty page cache at startup. | Publish the top node and its child nodes again. |
| Creating the package fails with a 409 error. | A package with that name already exists on the source. | Use a new name, or delete the old package. |