Dashboard

Compare environments

Diff two environments of a project side by side in the dashboard: keys that are missing, values that differ, and metadata that drifted.

Compare puts two environments of the same project next to each other and reports, key by key, what is missing on one side, what holds a different value, and what drifted in metadata. It lives in the dashboard only. There is no envless env compare, nothing in @goenvless/sdk, and no API endpoint that returns a diff, because the whole comparison runs in your browser on values the server never sees in the clear.

Compare works inside one project. Both selectors list that project's environments, so there is no cross-project and no cross-workspace comparison. Keys are matched by name, without regard to case, so API_KEY on one side lines up with api_key on the other rather than showing as two one-sided rows. Each side still displays the casing it stores. Variables marked deleted are excluded from both sides.

Open it

WhereHow
Environment pageThe kebab attached to Add variables in the page header, then Compare. When you cannot add variables the same menu sits behind a button labelled Actions. The environment you are on becomes the left side.
Project pageThe kebab next to Create environment, then Compare. Both sides start empty.
Environment cardThe card's kebab on the project page, then Compare. It navigates to that environment's page with the panel already open.

In the two page headers the item is only rendered when you hold variables:read and the project has more than one environment, so with a single environment the entry does not appear at all. The environment card's own menu is not gated that way and always offers Compare, which is harmless: without read access the panel simply cannot load either environment.

The URL is the comparison

Both sides live in the query string, so a comparison is a link you can paste into a pull request or a ticket:

ParamHoldsNotes
compareThe right side, as an environment slugThe panel opens whenever this param is present, even with an empty value. Every entry point above opens it as ?compare=, with no right side picked yet.
compareSourceThe left side, as an environment slugOptional on an environment page, where the left side falls back to the environment you are viewing. On the project page there is no such fallback, so without it nothing is selected.
plaintext
https://envless.cloud/dashboard/products/payments/projects/billing-api/environments/staging?compare=production
https://envless.cloud/dashboard/products/payments/projects/billing-api?compareSource=staging&compare=production

Once the panel is on screen, every write to these two params replaces the current history entry rather than pushing a new one, so opening it, swapping sides and closing it again adds nothing to your back button. The exception is the environment card menu, which is a navigation to another page and does push one entry. Closing removes both params.

A compare slug that matches no environment in the project resolves to nothing, and the panel falls back to Choose two environments to compare. or Choose an environment to compare against. A compareSource slug that matches nothing behaves exactly like a missing one, so on an environment page the left side still lands on the environment you are viewing. Picking, on one side, the environment already selected on the other swaps the two sides instead of comparing an environment with itself.

The five statuses

Every key that exists in either environment becomes one row, and every row carries exactly one status:

BadgeValueMeans
Only leftonly-sourceThe key exists in the left environment and not in the right one.
Only rightonly-targetThe key exists in the right environment and not in the left one.
DifferentdifferentThe key is in both, both values were decrypted in this browser, and the two plaintexts are not equal.
UnknownunknownThe key is in both, but at least one of the two values could not be turned into plaintext here: the workspace is locked, or the value did not decrypt with the current key.
IdenticalsameThe key is in both and either both values decrypted to the same plaintext, or the two stored ciphertexts are byte for byte identical.

Rows are sorted in exactly that order, then rows flagged as a missing required key first, then by key name.

Locked workspaces: names compare, values do not

This is the part to internalise before you trust a comparison.

Envless encrypts every value in your browser with AES-GCM under a fresh random 12 byte IV, so the same plaintext produces different ciphertext every single time it is written. Comparing stored ciphertext is therefore not symmetric:

  • Equal ciphertext proves the values are equal. Both environments sit in the same workspace and are encrypted under the same workspace key, so two byte identical envelopes can only hold the same plaintext.
  • Unequal ciphertext proves nothing at all. Two envelopes that differ may hold the same string, encrypted twice.

That asymmetry is why a fifth status exists. While the workspace is locked, a key present in both environments is reported as Identical when the ciphertext matches exactly, and Unknown otherwise. Different is never reported on a locked workspace, and the summary line suppresses it too.

Do not read Unknown as "different". It is the panel refusing to guess. On a locked workspace almost every shared key lands there, including keys whose values are identical, because two separate encryptions of the same string do not produce the same bytes. Unlock the workspace before you conclude that two environments have drifted.

While locked you get a banner reading Values are not compared over Unlock this workspace to compare values. Key names are shown below., every value cell is masked with no reveal, copy or edit control, and the summary counts shared keys as N in both, values not compared. Key names, presence and metadata are unaffected, since none of them are encrypted. See Passphrase for unlocking.

Unlocking decrypts both environments locally, eight values at a time, and caches each plaintext against the ciphertext it came from, so flipping the sides back, searching or switching filter does not re-run the work. That cache lives with the open panel: closing it discards every plaintext, and the next open decrypts from scratch. A value the current key cannot open shows Cannot decrypt with current key in place of the value, keeps its row on Unknown, and is counted in the summary as N could not be decrypted with the current key.

Metadata is compared even while locked

When a key exists on both sides, four metadata fields are compared alongside the value. None of them are encrypted, so this part of the comparison is exact whether the workspace is locked or not:

FieldValuesWhat differs
typestring, number, booleanThe declared type of the variable.
secretbooleanThe variable's Secret or Public marker. It is a label only, since every value in the dashboard is masked until you reveal it either way.
visibilityserver, clientWhere the variable is meant to be readable.
requiredbooleanWhether the variable is marked required.

A row with any of these mismatched gets a flag icon next to the key name, and its tooltip names the fields, for example Metadata differs: type, secret. A row can be Identical in value and still carry metadata drift, which is counted separately in the summary as N with metadata drift, and those rows survive the Differences filter rather than being hidden as identical.

The required flag on a missing key

A separate flag fires when a key exists on exactly one side and the side that has it marks the variable required. The key name then carries an amber required chip, and the row sorts to the top of its status group.

That is the highest signal row in the panel: one environment declares the variable mandatory and the other does not have it at all. Envless records required as metadata rather than enforcing it at read time, so this comparison is where a missing required key actually becomes visible.

Reading a value cell

You seeMeaning
Masked dotsThe value exists and is hidden. Click the eye to reveal it.
not setThe key does not exist in that environment.
emptyThe key exists and its value decrypts to an empty string.
Cannot decrypt with current keyThe value is present but did not open with the key currently unlocked.

Reveal is per cell, so the left and right value of the same key toggle independently. The reveal state is cleared whenever you change either side and whenever you close the panel, so a comparison never re-opens with values already on screen. A hover copy button puts the plaintext on the clipboard, and both controls are absent while the workspace is locked or the value could not be decrypted.

Double-clicking a value cell edits it in place, revealed or not, which requires variables:update, an unlocked workspace, a value that decrypted, and a project that is not locked by a plan limit. Enter saves and re-encrypts in the browser exactly like any other edit. While an inline edit is open, Esc no longer closes the panel: it cancels the edit instead, so one keystroke cannot throw away both the draft and the comparison.

Copy a variable across

Hovering a not set cell reveals a copy button that pulls the variable in from the other side. One click creates it in the environment whose column was empty, carrying seven fields over from the counterpart: name, value, type, secret flag, visibility, required, and stored default value.

The value crosses as ciphertext, byte for byte. Nothing is decrypted and nothing is re-encrypted on the way, because both environments belong to the same workspace and are sealed under the same workspace key, so the target environment can already read what the source wrote. That also means the copy works while the workspace is locked: no plaintext is needed to move it.

One consequence is worth knowing, because it is the single case where a locked comparison gives a positive answer. After a copy across, the two envelopes really are identical bytes, so that row reports Identical even with the workspace locked.

A successful copy raises Variable copied over The variable was created in this environment. Publish it when you are ready. The copy is a create, not a publish, so the new variable joins the environment's working set and only reaches version consumers once you publish.

Two failures surface as toasts. A name that already exists in the target environment is skipped server side and reports Variable not copied over Nothing was created. A variable with that name may already exist in this environment. Hitting the 1,000 variable fair-use cap on the target environment, which is the same number on every plan and is raised by support rather than by an upgrade, reports Limit reached over Some variables were not saved. Contact support to raise the limit.

Copy across only fills a gap, and only one variable per click. It never overwrites, so a key that exists on both sides with different values cannot be pushed from one side to the other here. It never deletes, so it cannot remove the extra key from the side that has it. It never syncs metadata onto an existing variable. There is no bulk copy and no "copy every missing key". For those, edit the value in place or work from the variables page.

Filter, search and summary

The panel opens filtered to Differences, which hides only the rows that are identical in value and carry no metadata drift. Switch to All keys to see every key in either environment.

Search matches key names only, as a case insensitive substring. Values are never searched, which is unavoidable: they are not necessarily decryptable, and on a locked workspace they are not readable at all.

Above the table sits a one line summary. When nothing differs it reads In sync, 12 keys identical. Otherwise it joins the applicable parts with a middle dot:

PartAppears when
N only in <left>Keys present on the left side only.
N only in <right>Keys present on the right side only.
N differentValues compared and found unequal. Never shown while locked.
N unknown, or N in both, values not compared while lockedShared keys whose values could not be compared.
N with metadata driftRows identical in value that still differ in type, secret, visibility or required.
N could not be decrypted with the current keyDistinct key names holding at least one value that failed to decrypt.
N identicalKeys equal on both sides.

When either environment's variables fail to load, the table is replaced by Could not load these environments. Close the panel and try again. When both load but hold no variables at all, the empty state reads These environments have no variables.

Permissions and limits

CapabilityNeeds
Open the panel and read the diffvariables:read, and at least two environments in the project.
Reveal or copy a value to the clipboardAn unlocked workspace and a value that decrypts.
Copy a variable acrossvariables:create, and a project not locked by a plan limit.
Edit a value inlinevariables:update, an unlocked workspace, a decrypted value, and a project not locked by a plan limit.

What it does not do

  • No CLI, SDK or API equivalent. A diff cannot be produced outside the browser, because outside the browser there is no plaintext to diff.
  • Two environments at a time, from one project. No three way comparison, no cross-project, no cross-workspace.
  • No export. The diff cannot be downloaded, copied as text, or attached to a version.
  • No comparison against a published version. This compares the current state of two environments. For history, see Version.
  • No rename detection. A key renamed in one environment reads as one Only left row plus one Only right row.
  • Values are not compared on a locked workspace, and unequal ciphertext never implies a difference.