---
title: "Sign an XML document - PKI Express"
description: "How to sign XML documents with PKI Express: XAdES of the whole document in two steps with Web PKI or with a server key, signing one element by its Id (NF-e, COD) and the XML signature policies."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/xml-signature"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-13"
---

# Sign an XML document - PKI Express

[PKI Express](https://docs.lacunasoftware.com/en-us/articles/pki-express) signs XML documents on your server: the whole document or one element, in two steps with the user's certificate
and [Web PKI](https://docs.lacunasoftware.com/en-us/articles/web-pki), or in one step with a key held by the server. 
The `start-xml`, `complete-sig` and `sign-xml` commands exist since PKI Express 1.0.0. 

The code is Node.js, from the [PKI Suite samples](https://github.com/LacunaSoftware/PkiSuiteSamples), with `pki-express` (1.16.2), and PHP
with `lacuna/pki-express` (1.17.0) for the element signatures. Only the Node.js, Java and PHP libraries sign XML; the Python and Ruby
libraries have no XML classes. 

## Flow with the user's certificate

The flow is the one of the [PDF signature](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#flow), with `XmlSignatureStarter`:

1. **Browser**: Web PKI reads the user's certificate; the page posts its content.
1. **Server**: `XmlSignatureStarter.start()` returns the hash to sign, its digest algorithm and a transfer file.
1. **Browser**: Web PKI signs the hash with `signHash()`.
1. **Server**: `SignatureFinisher.complete()` with the same XML writes the signed XML.

## Sign the whole document

```js
const signatureStarter = new XmlSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.XADES_BES;
signatureStarter.setXmlToSignFromPathSync(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start()
	.then((response) => {
		const { toSignHash } = response;
		const { digestAlgorithm } = response;
		const { transferFile } = response;
		// render the page that signs the hash with Web PKI's signHash()
	}).catch((err) => next(err));
```

Then complete it as a PDF signature, with the XML as the file to sign and an `.xml` output: 

```js
const signatureFinisher = new SignatureFinisher();
Util.setPkiDefaults(signatureFinisher);
signatureFinisher.setFileToSignFromPathSync(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
signatureFinisher.setTransferFileFromPathSync(transferFile);
signatureFinisher.signature = signature;
const outputFile = `${uuidv4()}.xml`;
signatureFinisher.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
signatureFinisher.complete(true).then((certificate) => { /* signed XML at outputFile */ });
```

## Sign with a server key

`XmlSigner` signs in one call, with no browser; the sample uses a test PKCS #12 file with password `1234`, or one generated from a
certificate issued by the Amplia sample. 

```js
const signer = new XmlSigner();
Util.setPkiDefaults(signer);
signer.signaturePolicy = StandardSignaturePolicies.XADES_BES;
await signer.setXmlToSignFromPath(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
await signer.setPkcs12FromPath(StorageMock.getSamplePkcs12Path());
signer.certPassword = '1234';
const outputFile = `${uuidv4()}.xml`;
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);
```

The other key sources (a certificate thumbprint, a key name, a cloud certificate) are the same as for PDFs: see
[Sign with a server key](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#server-key). 

## Sign one element

Set `toSignElementId` to the value of the `Id` attribute of the element to sign; PKI Express receives it as `--element-id`. 

### Brazilian electronic invoice (NF-e)

The NF-e sample signs the `infNFe` element with the NF-e national standard policy: 

```php
$signatureStarter = new XmlSignatureStarter();
Util::setPkiDefaults($signatureStarter);
$signatureStarter->signaturePolicy = StandardSignaturePolicies::NFE_PADRAO_NACIONAL;
$signatureStarter->toSignElementId = 'NFe35141214314050000662550010001084271182362300';
$signatureStarter->setXmlToSign(StorageMock::getSampleDocPath(SampleDocs::SAMPLE_NFE));
$signatureStarter->setCertificateBase64($certContent);
$response = $signatureStarter->start();
```

`complete()` then works as above, with `SignatureFinisher` and the same XML. 

### Certificate of origin (COD)

The COD sample makes two chained element signatures on a COD envelope: first the `COD` element, then `CODEH` on the file the first signature
produced, each with the COD policy and in two steps. 

```php
$signatureStarter->signaturePolicy = StandardSignaturePolicies::COD_WITH_SHA1;
$signatureStarter->toSignElementId = 'COD';    // then 'CODEH' on the signed file
```

### Where the signature goes

Node.js (`setSignatureElementInsertion`) and Java (`setSignatureElementInsertion` with `XmlElementInsertion`) choose where the signature
element is inserted: `append-child`, `prepend-child`, `append-sibling` or `prepend-sibling`. This needs PKI Express 1.26.0 or later. 
TODO(docs): where PKI Express inserts the signature by default.

## Signature policies

| Constant (Node.js) | Value | Notes |
|---|---|---|
| `XADES_BES` | `xades` | |
| `XML_DSIG_BASIC` | `basic` | XMLDSig; PKI Express 1.32.0 added a SHA-1 variant for legacy signatures only |
| `NFE_PADRAO_NACIONAL` | `nfe` | NF-e |
| `PKI_BRAZIL_XML_ADR_BASICA` | `adrb` | ICP-Brasil |
| `PKI_BRAZIL_XML_ADR_TEMPO` | `adrt` | ICP-Brasil; needs a timestamp authority |
| `PKI_BRAZIL_XML_ADR_COMPLETA` | `adrc` | ICP-Brasil; needs a timestamp authority |
| `PKI_BRAZIL_XML_ADR_ARQUIVAMENTO` | `adra` | ICP-Brasil, PKI Express 1.25.0 or later; needs a timestamp authority |
| `COD_WITH_SHA1`, `COD_WITH_SHA256` | `cod-sha1`, `cod-sha256` | COD, PKI Express 1.6 or later |
| `COD_WITH_SHA256_EC14N` | `cod-sha256-ec14n` | COD with the extra `xml-exc-c14n#` transform, PKI Express 1.36.0 |

* Java names the constants in camel case (`XadesBes`, `NFePadraoNacional`, `CodWithSHA1`) and adds `XadesBrazil` (`xades-brazil`) and
  `PkiBrazilXadesWithCertProtection`. 
* TODO(docs): what the `xades-brazil` and `pki-brazil-with-cert-protection` generation policies produce.
* TODO(docs): which client libraries set the `classic-enveloped-transform` option that PKI Express 1.27.0 added; only Java has it
  (`setUseClassicEnvelopedTransform`). 

## In each language

| Library | XML (start) | Element | Server key |
|---|---|---|---|
| Node.js | `XmlSignatureStarter.setXmlToSignFromPath` | `toSignElementId` | `XmlSigner` |
| Java | `XmlSignatureStarter.setXmlToSign` | `setToSignElementId` | `XmlSigner` |
| PHP | `XmlSignatureStarter.setXmlToSign` | `toSignElementId` or `setToSignElementId` | `XmlSigner` |

## Errors

A failed command ends with a PKI Express return code, such as 14 (`ValidationFailed`) or 32 (`TimestampOptionsRequired`). See
[Return codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes). 

## See also

* [Validate signatures](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures#xml), to open a signed XML
* [Sign a PDF (PAdES)](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature) and [Sign with CAdES](https://docs.lacunasoftware.com/en-us/articles/pki-express/cades-signature)
* [Cloud certificate services](https://docs.lacunasoftware.com/en-us/articles/pki-express/config/trust-services), for the cloud-certificate XML sample
* [PKI Express quick reference](https://docs.lacunasoftware.com/en-us/articles/pki-express/quick-reference)

**Demos and samples**

- Sign an XML document using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-signature-express.js)
- Sign an XML document with a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-server-key-express.js)
- Sign a Brazilian NF-e using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/xml-nfe-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-nfe-signature-express/index.php)
- Sign a COD XML using PKI Express: [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/xml-cod-signature-express/index.php)
