nextcloud/server · error · BadRequest

vCards on CardDAV servers MUST have a UID property

Error message

vCards on CardDAV servers MUST have a UID property

What it means

CardDavBackend::getUID() parses card data with Sabre\VObject\Reader and requires a UID property; without one it throws BadRequest('vCards on CardDAV servers MUST have a UID property') (HTTP 400). RFC 6352 makes UID mandatory for address object resources, and Nextcloud keys card URIs on it, so UID-less cards are rejected on create/update.

Source

Thrown at apps/dav/lib/CardDAV/CardDavBackend.php:1580

		}
	}

	/**
	 * Extract UID from vcard
	 *
	 * @param string $cardData the vcard raw data
	 * @return string the uid
	 * @throws BadRequest if no UID is available or vcard is empty
	 */
	private function getUID(string $cardData): string {
		if ($cardData !== '') {
			$vCard = Reader::read($cardData);
			if ($vCard->UID) {
				$uid = $vCard->UID->getValue();
				return $uid;
			}
			// should already be handled, but just in case
			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
		}
		// should already be handled, but just in case
		throw new BadRequest('vCard can not be empty');
	}

	/**
	 * Mark all cards in an address book as needing to be validated
	 *
	 * This is done by setting the modified date to `null`, once a sync runs
	 * the mtime will be set to a non-null value. Leaving all deleted items with
	 * a null modified date.
	 */
	public function markCardsAsPending(int $addressBookId): void {
		$query = $this->db->getTypedQueryBuilder();
		$query->update($this->dbCardsTable)
			->set('lastmodified', $query->createNamedParameter(null))
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
			->executeStatement();

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Generate a UID (UUID v4) for every card before PUT
  2. Pre-parse imported vcards and inject a 'UID:<uuid>' line where missing
  3. On the 400, patch the card with a UID and re-submit once

Example fix

// before
BEGIN:VCARD
VERSION:4.0
FN:Ada Lovelace
END:VCARD
-> 400 vCards on CardDAV servers MUST have a UID property

// after
BEGIN:VCARD
VERSION:4.0
UID:8f0f9a5e-6c1e-4a2b-9d3f-7e5c1b2a3d4e
FN:Ada Lovelace
END:VCARD
Defensive patterns

Strategy: validation

Validate before calling

$vcard = \Sabre\VObject\Reader::read($cardData);
if (!$vcard->UID) {
    $vcard->UID = $uuidFactory->uuid4()->toString();
    $cardData = $vcard->serialize();
}

Type guard

function cardHasUid(string $cardData): bool
{
    $v = \Sabre\VObject\Reader::read($cardData);
    return $v !== null && (bool) $v->UID;
}

Try / catch

try {
    $backend->createCard($bookId, $uri, $cardData);
} catch (\Sabre\DAV\Exception\BadRequest $e) {
    if (str_contains($e->getMessage(), 'UID property')) {
        $vcard = \Sabre\VObject\Reader::read($cardData);
        $vcard->UID = $uuidFactory->uuid4()->toString();
        $backend->createCard($bookId, $uri, $vcard->serialize()); // retry once
    }
}

Prevention

When it happens

Trigger: PUT of a vCard whose body has no UID: line; importing .vcf files produced by tools that omit UID; hand-built VCARD strings in tests or migration scripts.

Common situations: Third-party exporters (CSV converters, CRM dumps) that skip UID; cards truncated in transit so the UID line is cut off; clients that build cards without a UUID generator.

Related errors


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/d86e2f600a978123. Report an issue: GitHub.