On this page

Package Format

Introduction

The application manager uses a very simple package format: a standard UNIX gzip-compressed TAR archive. Metadata is embedded as normal files, but using the reserved name prefix --PACKAGE-. Even though USTAR tar archives support a lot of features, the application manager only supports standard files and directories with relative paths only (using ../ in a path is not allowed). Modes, except the owner's x bit, are ignored.

This makes it very easy to write custom packagers as well as custom app-store server backends, since TAR archive handling is available as a utility library in any programming language.

Note: gzip is currently hardcoded, but since the (de)compression is done by libarchive, this could easily be replaced by bzip2 or xz compression. Both would lead to additional 3rd-party dependencies on Windows though.

These are the important files in a package:

FileDescription
--PACKAGE-HEADER--Exactly one YAML document that needs to be the first file in the package - see below for a format description. This file is used as format selector.
info.yamlThe manifest of the application. The packaging tool needs to make sure that this file is the first non-meta-data entry of the tar archive. See Manifest Definition for the documentation of the file format.
an iconThe icon of the application that can be used in an app-store. Can also be used as the app's icon in a launcher menu on the device. This is optional, but if info.yaml specifies an icon, then the packaging tool needs to make sure that this icon file is the second non-meta-data entry of the tar archive. It can be in any image format that Qt supports. The file size cannot exceed 1MB.
--PACKAGE-FOOTER--One or more YAML document(s) that need to be the last file(s) in the package - see below for a format description. If adding more than one footer (e.g. appending the store signature by the app-store server), an arbitrary string can be appended to the file name (e.g. --PACKAGE-FOOTER--storesig). This makes it easier to troubleshoot packages, since you can then easily extract them with the standard tar command line tool.

In short, here is what is NOT allowed inside a package:

  • You are not allowed to have file names that start with --PACKAGE-
  • You are not allowed to have absolute paths
  • You are not allowed to have ../ in relative paths
  • You are not allowed to have sym-links, hard-links, device files, sockets, etc.
  • File modes except the owner's x bit are ignored.

The Checksum Algorithm

All of package data, as well as important meta-data is protected by a SHA256 digest. This checksum has two purposes: it protects against transmission errors, and the package signing is done by encrypting this checksum.

The algorithm is selected by the formatVersion field of the --PACKAGE-HEADER--: version 3 uses the algorithm described in the first section below, versions 1 and 2 use the legacy algorithm described in the second section.

The generated digest is put into the --PACKAGE-FOOTER-- as a 32-byte hex-encoded string.

Format version 3

The digest input is a single byte stream that is unambiguous to parse from left to right: every variable-length field is preceded by its length, and every other field has a fixed width. Different packages therefore can never produce the same byte stream. All values are written with QDataStream, using the stream version Qt_6_7 (big endian). The stream consists of the following parts, in this order:

stream  := TAG header entry*
TAG     := "AM" 0x00 "PKG3" 0x00
header  := 'H' QVariant(QVariantMap)
entry   := 'E' type QString(path) quint64(size) ('X' bytes(name) bytes(value))* [ 'C' <size bytes> ]
type    := 'F' | 'D'
bytes   := quint32(length) <length bytes>
  • TAG is the 8 bytes 41 4D 00 50 4B 47 33 00. It is the first thing hashed.
  • 'H', 'E', 'X', 'C' and type are single quint8 values.
  • header is a QVariantMap with the following four entries: formatVersion (the integer 3), packageId (string), extendedAttributes (bool; false if absent in the header) and extraSigned (map; empty if absent in the header). All other header fields (e.g. extra and diskSpaceUsed) are not part of the digest.
  • There is one entry for every file and directory, in the order they appear in the archive (excluding the --PACKAGE-* meta-data files). type is 'F' for files and 'D' for directories, path is the path (normalized to Unicode normalization form C, without a trailing slash) and size is the size of the file content in bytes (always 0 for directories).
  • If extended attributes are included in the package (extendedAttributes in the header), then every attribute of the entry is added as 'X', name and value in the order they appear in the archive.
  • The 'C' section, containing exactly size bytes of the file's content, is only present for files.

Format versions 1 and 2 (legacy)

Note: This algorithm does not guarantee that different packages generate different digests. It is only supported to be able to verify existing packages, and should not be used for new packages. The installer/minimumPackageFormatVersion configuration option controls whether such packages are accepted.

  • If the extraSigned field is present in the header, it is converted to a QVariantMap and serialized with QDataStream (using the Qt 6.7 data stream version), and the result is added to the digest.
  • For every file that is not a --PACKAGE-* meta-data file, the extended attributes (if included in the package) are added to the digest, each as the UTF-8 encoded string "XATTR/<name>/<value>". Then the complete content is added to the digest. Afterwards the following UTF-8 encoded string is also added to the digest: "F/<file size in bytes>/<file path>" (e.g. the file foo/test.qml with a size of 250 bytes would generate F/250/foo/test.qml)
  • For every directory, the extended attributes (if included in the package) and then the following UTF-8 encoded string is added to the digest: "D/0/<directory path>" (e.g. the directory foo/images would generate D/0/foo/images)

The Signing Algorithm

The package format currently supports two signatures: a developer signature (generated by the developer before uploading to an appstore-server) and a store signature (generated by the appstore-server before the client actually downloads a package).

Both signatures are calculated the same way and are appended to the package's meta-data the same way: The binary digest is used as an input to create a detached PKCS7 signature. This PKCS7 signature is then BASE64 encoded and stored in --PACKAGE-FOOTER-- - either in the developerSignature or storeSignature field.

There is also a separate page documenting the required Public Key Infrastructure.

If you want to implement this signing algorithm yourself, then please look at these four existing implementations:

  • C++ OpenSSL based in src/crypto-lib/signature_openssl.cpp
  • C++ WinCrypt based in src/crypto-lib/signature_win.cpp (Windows only)
  • C++ SecurityFramework based in src/crypto-lib/signature_macos.cpp (macOS only)
  • Python m2crypt based in the appstore-server reference implementation.

The fields within the --PACKAGE-FOOTER-- and --PACKAGE-HEADER-- file headers (first YAML document) are as follows:

Field NameTypeDescription
formatVersionintRequired. For footers always 2. For headers 3 (see The Checksum Algorithm) or 2. Packages with the legacy header version 1 can still be read, but no longer be created.
formatTypestringRequired. Always am-package-header for headers or am-package-footer for footers.

The fields within the --PACKAGE-FOOTER-- and --PACKAGE-HEADER-- data (second YAML document) are as follows:

Field NameTypeDescription
idstringThe unique ID of the package. This has to match the ID of the package as described by the info.yaml manifest file.
iconstringRequired. The icon's file name. The file must be located in the same directory as info.yaml and can be in any image format that Qt supports.

Note: The old format (pre 5.14) had the formatVersion header field set to 1 and used the field name applicationId instead of packageId.

Example Package

This is an example of a minimal QML application package. The actual package can be created by tar'ing all these files up with

tar cvzf qmlapp.ampkg ./--PACKAGE-HEADER-- info.yaml icon.png main.qml ./--PACKAGE-FOOTER--
FileContents
--PACKAGE-HEADER--
%YAML 1.1
---
formatType: am-package-header
formatVersion: 2
---
packageId: com.pelagicore.minimal
diskSpaceUsed: 1000
info.yaml
%YAML 1.1
---
formatType: am-package
formatVersion: 1
---
id: 'com.pelagicore.minimal'
icon: 'icon.png'
name:
  en: 'Minimal App'
applications:
- id: com.pelagicore.minimalapp
  code: 'main.qml'
  runtime: 'qml'
icon.pnga standard png image
main.qmlyour QML application
--PACKAGE-FOOTER--
%YAML 1.1
---
formatType: am-package-footer
formatVersion: 2
---
# SHA256 digest in 32 char hex representation
digest: '9df5635bb50e93846954c6377468c07835119e2835475ec90b3e9a9f7261cf27'

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.