/**
* @param ApplyChanges $changes
* @return ApplyResult
*/
public function apply(
string $project,
string $environment,
array $changes,
?string $product = null,
int|float|null $timeout = null,
): arrayRuns creates, updates and deletes against an environment inside one transaction, behind a per environment advisory lock. This is the method a sync client should use instead of a loop of single calls: the environment is never left half written and two concurrent applies cannot interleave.
baseline is required and is how drift is detected. Send every live variable as you last read it, as an array of uniqueId, name and updatedAt filled from the id, name and updatedAt of a read. Before touching anything the server compares your baseline against the live rows and refuses the whole request with 409 variable.baseline_drifted when a live variable is missing from your baseline, a baseline entry no longer exists, or an updatedAt differs by a single millisecond. A partial baseline is not a partial check, it is guaranteed drift. The drifted names appear only in the error message, so on a 409 re-read the environment and rebuild the change set.
Apply is value only and name only. creates write name and value and take the column defaults for everything else, updates write value alone, and there is no rename and no defaultValue path here, use update or updateById for those. Every creates and updates value goes through the same v1:/v2: prefix check create enforces, and one plaintext value refuses the whole change set with 400 variable.ciphertext_invalid before anything is written.
Every creates name must match ^[A-Za-z_][A-Za-z0-9_]*$ and be 1 to 100 characters, and is stored with exactly the casing you send. Names are compared without regard to case, so two creates entries differing only in case are a repeat, and creating api_key while API_KEY is live and not in deletes is a conflict. baseline names are not pattern checked, send each one back exactly as you read it so a legacy name still round-trips.
Example
use Envless\Envless;
$envless = new Envless();
$passphrase = (string) getenv('ENVLESS_PASSPHRASE');
$workspaceId = $envless->me->get()['workspaceId'];
$baseline = [];
$deletes = [];
foreach ($envless->variables->listAll('api', 'production') as $variable) {
$baseline[] = [
'uniqueId' => $variable['id'],
'name' => $variable['name'],
'updatedAt' => $variable['updatedAt'] ?? $variable['createdAt'],
];
if (str_starts_with($variable['name'], 'LEGACY_')) {
$deletes[] = ['uniqueId' => $variable['id']];
}
}
$flag = Envless::encryptValue('true', $passphrase, $workspaceId);
$result = $envless->variables->apply('api', 'production', [
'baseline' => $baseline,
'creates' => [['name' => 'FEATURE_FLAG', 'value' => $flag]],
'deletes' => $deletes,
]);
echo "{$result['added']} {$result['updated']} {$result['removed']}", PHP_EOL;Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
$project | string | Yes | Project slug the environment belongs to. |
$environment | string | Yes | Environment slug to sync. |
$changes | ApplyChanges | Yes | The fields below, as one array. |
$changes['baseline'] | list<ApplyBaselineEntry> | Yes | Every live variable as you last read it, up to 1000 entries. |
$changes['creates'] | list<VariableInput> | No | New variables, where only name and value are written. name matches ^[A-Za-z_][A-Za-z0-9_]*$, 1 to 100 characters. |
$changes['updates'] | list<ApplyUpdateEntry> | No | Value replacements addressed by uniqueId. Add name to rename the variable in the same transaction; it is validated like a create name. |
$changes['deletes'] | list<array{uniqueId: string}> | No | Soft deletes addressed by uniqueId. |
$product | string | No | Product slug, needed when the project slug is not unique in the workspace. |
$timeout | int|float | No | Seconds each attempt of this call may take, the response body included, before it throws Envless\Exception\NetworkException with isTimeout() true. Overrides the client's timeout for this call, and 0 turns the limit off. |
Returns
ApplyResult with added, updated and removed counts for what the transaction wrote.
Worth knowing
- At most 1000 baseline entries, and at most 1000 items across
creates,updatesanddeletescombined. At least one of the three must be non-empty. - 409
variable.baseline_driftedis reused for acreatesname already taken and for anupdatesordeletesid that is no longer live, so readmessageto tell the three apart. - A failed apply writes nothing at all, so re-reading and retrying is always safe.
- Names are case reserved, not case normalised. A name freed by a
deletesentry in the same request can be reused by acreatesentry, including under a different casing. - Every
createsname is checked locally before anything is sent, so an unusable name throwsEnvless\Exception\ApiExceptioncarryingrequest.body_invalidwithout touching the transaction.
Required scopes
variables:write
Underlying endpoint
POST /projects/{slug}/environments/{environmentSlug}/variables/apply
Request and response schema, every error code, and a live playground.
In TypeScript
envless.variables.apply()
The same method in @goenvless/sdk, with the same request, retries and errors.