Moving Content Between Umbraco Sites with a Package

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.

What to Expect

When the package carries pages and images only:

Prerequisites

Creating the Package on the Source Site

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:

The page to send, as it appears on the source site

Using the Backoffice

For more information, see Creating a Package Schema in the Backoffice in the Umbraco documentation.

  1. Navigate to the Packages section.
  2. Select Created in the top-right corner of the screen.
  3. Click Create package.
  4. Enter a name for the package. Package names must be unique on the site, so number them, for example Site Pages 1.
  5. Under Content, select the top node and include its child nodes.
  6. Under Media, select the images the pages use.
  7. Leave every other field empty: Document Types, Media Types, Data Types, Templates, Stylesheets, Scripts, Partial Views, Languages, and Dictionary.
  8. Click Create, then Download.

The download is a package.zip file. The ZIP file contains package.xml and the image files.

Using the Management API

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.

  1. 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": []
    }
  2. Download the package, using the ID from the Location header of the first response:

    GET /umbraco/management/api/v1/package/created/<id>/download

Checking What the Package Needs

Run package_needs.py on the downloaded file:

python3 package_needs.py package.zip

The 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.

Checking the Target’s Document Types

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.

Adding the Package to the Target’s Code

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.

  1. 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") { }
    }
  2. 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).

Deploying the Target

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.

Checking the Result

In the target’s backoffice, open the Content section and check that:

The target’s backoffice after the install: the new pages sit beside the target’s existing content

Publishing the Pages

Publish the pages on the target when you are ready. Umbraco builds each page’s URL from its name.

The page published on the target, matching the source

Sending the Next Batch

  1. Create a new package on the source, for example Site Pages 2.
  2. Replace ContentImport/package.zip on the target with the new file. Keep the same file name, class, and name passed to base.
  3. Build, deploy, and restart the target.

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.

Sending a Correction

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:

  1. Make the correction on the source.
  2. On the target, delete the page.
  3. Empty the target’s recycle bin.
  4. Send a new package that includes the page, as described in Sending the Next Batch.
  5. Publish the corrected page on the target.
The corrected page on the source
The corrected page on the target, after the old copy was deleted and the recycle bin emptied

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.

Troubleshooting

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.

Further Reading