---
title: "DropletID, DroppyKit"
description: "Stable identifier of a droplet across the whole system."
canonical: "https://getdroppy.app/docs/droppykit/api/droplet-id"
last-updated: "2026-09-09"
---

# DropletID

Stable identifier of a droplet across the whole system.

`struct DropletID`

The same string appears in three places and must match in all of them: the `DroppyDropletID` key of the bundle's Info.plist, the `id` field of the store manifest entry, and the `Droplet.id` static property of the runtime object. The loader refuses bundles where these disagree.

Use a short, lowercase, url-safe string, for example `"worldclock"`.

The string is not only an identifier. The host builds a filesystem path component from it (`~/Library/Application Support/Droppy/Droplets/<id>/`) and a preference key prefix (`droplet.<id>.`), which makes an unchecked id a path-traversal and namespace-collision hazard. `isValid(_:)` is the charset that closes both, and [`DropletBundleInfo`](https://getdroppy.app/docs/droppykit/api/droplet-bundle-info) refuses a bundle whose plist id fails it, before any of that bundle's code is loaded.

## Initializers

### init(_:)

`init(_ rawValue: String)`

Creates an identifier from a raw string.

### init(from:)

`init(from decoder: Decoder) throws`

### init(rawValue:)

`init(rawValue: String)`

Creates an identifier from a raw string.

### init(stringLiteral:)

`init(stringLiteral value: String)`

String-literal convenience, so `static let id: DropletID = "worldclock"` reads naturally.

### init(validating:)

`init?(validating rawValue: String)`

Creates an identifier, returning `nil` when `rawValue` is not a valid droplet id.

The unlabelled and `rawValue:` initializers stay unchecked on purpose: they are how a host reconstructs an id it already trusts, and how a droplet writes its own `static let id` literal. Use this one for anything that came from outside the process.

## Type properties

### validLengthRange

`static let validLengthRange: ClosedRange<Int>`

Length bounds a droplet id must fall inside, inclusive.

## Properties

### description

`var description: String { get }`

### isValid

`var isValid: Bool { get }`

Whether this identifier satisfies `isValid(_:)`.

### rawValue

`let rawValue: String`

The raw string value of the identifier.

## Type methods

### isValid(_:)

`static func isValid(_ rawValue: String) -> Bool`

Whether `rawValue` is a usable droplet id.

The rule: 3 to 40 characters, ASCII letters, digits and hyphens only, starting with a letter and ending with a letter or digit. That rejects every character that could turn an id into something other than one path component or one preference namespace:

- `/` and `\` and `..` cannot appear, so an id can never escape the directory the host builds for it.
- `.` cannot appear, so `droplet.<id>.<key>` has exactly one meaning. Without that rule, droplet `a` writing key `b.c` and droplet `a.b` writing key `c` are the same default.
- No spaces, no leading or trailing hyphen, no empty string.

Uppercase ASCII letters are accepted because in-tree droplets shipped with camelCase ids before this rule existed. The store's submission intake is deliberately stricter and requires lowercase; this function is the safety floor that runs on the user's machine, not the style rule for new public submissions. Hosts comparing ids for uniqueness should fold case, because the id becomes a path component and macOS filesystems are usually case-insensitive.

## Methods

### encode(to:)

`func encode(to encoder: Encoder) throws`
