Envless ships three independent packages: @goenvless/cli, the terminal binary, @goenvless/env, the runtime your application imports, and @goenvless/sdk, the API client. They are not layered on each other and none is a wrapper around another, so picking the right one matters.
@goenvless/cli | @goenvless/env | @goenvless/sdk | |
|---|---|---|---|
| What it is | The envless binary | The application runtime | The programmatic admin client |
| Installed as | A global binary, npm i -g @goenvless/cli | A project dependency, npm i @goenvless/env | A project dependency, npm i @goenvless/sdk |
| Importable | No. Its only export is ./package.json | Yes, nine entrypoints: ., /server, /client, /edge, /next, /vite, /eslint, /crypto, /register | Yes, one root entrypoint |
| Authenticates with | A device session, or a machine key for the read-only commands | ENVLESS_TOKEN, plus ENVLESS_KEY or ENVLESS_PASSPHRASE | An API key |
| Runs | On a developer machine or a CI runner | Node or Bun for the server entrypoints, the browser for /client, anywhere fetch exists for /edge, anywhere Web Crypto exists for /crypto | Anywhere fetch exists, except a browser. The transport throws there unless you pass dangerouslyAllowBrowser |
| Touches the filesystem | Yes: .env, .envless, the local cache | Yes on the server. The ., /server, /register, /next and /vite entrypoints import node:fs, node:os and node:path to read .envless, the CLI config and the on-disk cache, so none of them runs on an edge runtime. /client, /edge, /crypto and /eslint import no Node builtin | Never |
| Spawns processes | Yes: envless run and envless build | Never | Never |
| Loads variables into an app | No. It injects them into a child process (envless run, envless build), minus the names that would change how that process executes code, or writes them to a file (envless sync), from outside the app | Yes. This is the only package that puts variables in your import graph. load() also writes them into process.env, minus the same refused names | No |
| Administers a workspace | Yes, interactively | No | Yes, all of it |
The SDK column holds for envless-sdk, the Python SDK, as well: the same API client with the same methods, installed with pip install envless-sdk. A Python application loads its variables with envless-env, the Python twin of @goenvless/env, or from the CLI through envless run -- python app.py or envless sync.
The same goes for envless-sdk, the Ruby SDK, installed with bundle add envless-sdk. A Rails or Rack application loads its variables with envless-env, the Ruby twin of @goenvless/env, or from the CLI through envless run -- bin/rails server or envless sync.
PHP has both too. envless/sdk is the API client, installed with composer require envless/sdk, and a Laravel, Symfony or plain PHP application loads its variables with envless/env, the PHP twin of @goenvless/env, or from the CLI through envless run -- php artisan serve or envless sync.
The dividing line is who is driving. The CLI is for a human at a terminal on a machine that holds a device session. The runtime is for the application itself, reading its own configuration at boot. The SDK is for a program holding an API key, with no terminal, no home directory and nobody to prompt.
@goenvless/env is the runtime, not "the runtime SDK". The SDK is @goenvless/sdk, a different package that never loads variables into your app.
Command to method map
Every envless command that changes workspace state has an SDK equivalent. The device operations further down do not, including logout, which revokes the session on the server.
Products, projects, environments
| CLI | SDK |
|---|---|
envless product list | products.list |
envless product create | products.create |
envless product update | products.update |
envless product delete | products.delete |
envless product duplicate | products.duplicate |
envless project list | projects.list |
envless project create | projects.create |
envless project rename / update | projects.update |
envless project delete | projects.delete |
envless project duplicate | projects.duplicate |
envless env list | environments.list |
envless env create | environments.create |
envless env rename | environments.update |
envless env delete | environments.delete |
envless env duplicate | environments.duplicate |
envless env expose | environments.expose and unexpose |
Variables and versions
| CLI | SDK |
|---|---|
envless var list | variables.list |
envless var get | variables.get |
envless var set | variables.create or update |
envless var remove | variables.delete |
envless var history | variables.listHistory |
envless push | variables.apply |
envless version list | versions.list |
envless version get | versions.get |
envless publish / version publish | versions.create |
envless version restore | versions.rollback |
Workspace, people and integrations
| CLI | SDK |
|---|---|
envless whoami | me.get |
envless workspace list | workspaces.list |
envless workspace create | workspaces.create |
envless workspace update | workspace.update |
envless workspace delete | workspace.delete |
envless workspace virtual-envs | workspace.update with virtualEnvsEnabled |
envless domain claim | workspace.domain.claim |
envless domain status | workspace.domain.get |
envless domain verify | workspace.domain.verify |
envless domain remove | workspace.domain.remove |
envless member list | members.list |
envless member invite | members.invite |
envless member update | members.update |
envless member remove | members.remove |
envless member resend | invites.resend |
envless member leave | members.leave |
envless role list | roles.list |
envless role create | roles.create |
envless role update | roles.update |
envless role delete | roles.delete |
envless role permissions | roles.listPermissions |
envless access list | products.access.list |
envless access grant | products.access.grant |
envless access revoke | products.access.revoke |
What only the CLI can do
These are device operations. They depend on a local filesystem, a terminal, or a browser round-trip, so the SDK deliberately has no equivalent.
| Command | Why it is CLI-only |
|---|---|
envless login / logout | A device authorisation flow through the browser. The SDK's credential is an API key, which needs no flow. |
envless init / link | Both write a local .envless file that binds this directory to a project. init creates the project and its environments first. |
envless sync / pull | Writes a decrypted .env file to disk. |
envless run / build | Spawns a child process with the decrypted variables in its environment, minus the names that would change how it executes code. |
envless types | Writes envless-env.d.ts, the declaration that types both env and process.env. |
envless cache clear | Clears the on-disk ciphertext cache, per environment or with --all. |
envless passphrase | Caches the workspace encryption key in local config, or wipes it. |
envless workspace use | Changes which workspace the local session points at. An API key is bound to one workspace, so there is nothing to switch. |
envless push and envless sync do have SDK halves. The API side of push is variables.apply and the API side of sync is variables.listAll. What the SDK will not do is read or write the .env file at either end.
What only the runtime can do
@goenvless/env is the only package your application code imports, and the only one that puts decrypted values in front of that code:
| Entrypoint | Why neither the CLI nor the SDK replaces it |
|---|---|
@goenvless/env/server | An env proxy over every variable, coerced to its declared type, plus __override() for tests. Its per-variable types arrive when envless types writes envless-env.d.ts, which augments the EnvlessServerEnv interface. |
@goenvless/env/client | An env proxy filtered to client-visibility variables, hydrated synchronously from a payload the bundler injected. Reading a server-only variable throws. |
@goenvless/env/edge | load({ token, project, environment, product?, passphrase? or key?, workspaceId?, endpoint?, fetch? }) for a runtime with no filesystem. It returns a plain record and imports no Node builtin, so it never writes process.env, never fills the env proxy, and keeps no cache. |
@goenvless/env/crypto | encrypt, decrypt, encryptWithKey, decryptWithKey, workspaceKey, importWorkspaceKey, passphraseStrength and ENCRYPTION_PARAMETERS, on Web Crypto alone. No Node builtin and no Buffer, so it runs wherever crypto.subtle does. |
@goenvless/env/register | The only entrypoint that calls load() on import, with a top-level await. Importing . or /server auto-loads nothing, so /register is what a bare node --import @goenvless/env/register preload uses. require()ing it throws, since it cannot block. |
@goenvless/env/next, /vite | Framework plugins that await load() during config evaluation and inline client values into the bundle. |
@goenvless/env/eslint | The no-server-import-in-client rule that keeps /server out of client files. |
The root @goenvless/env entrypoint exports env, load, encrypt, decrypt, passphraseStrength and ENCRYPTION_PARAMETERS. Its env proxy is neither typed per variable nor filtered by visibility, and importing it loads nothing, so reach for /server or /client in application code.
What only the SDK can do
A couple of things the CLI has no command for, plus several it will not do conveniently:
- Administer a workspace from inside a Cloudflare Worker, a Lambda or a container, with no binary to install and no filesystem to read.
@goenvless/env/edgeloads variables in those runtimes, but it administers nothing. - Write to a workspace in CI with nothing but an API key. The same
ev_sk_key reaches the CLI and the runtime, but every CLI command that writes refuses it withMachine keys are read only. - Compose many operations in one typed script, with pagination and typed errors handled for you.
- Verify an incoming webhook signature.
- Rotate a passphrase across both live variables and version history in one call.
What it will not do is load variables into your application. That is @goenvless/env.
Using them together
The three coexist without interfering. A common split is the CLI for humans, the runtime for the app itself, and the SDK for automation, in the same repository:
# @goenvless/cli: a developer, at a terminal
npm i -g @goenvless/cli
envless login
envless link
envless run -- npm run dev// @goenvless/env: the application reading its own configuration,
// started with node --import @goenvless/env/register
import { env } from '@goenvless/env/server'
const stripe = new Stripe(env.STRIPE_SECRET)// @goenvless/sdk: scripts/provision.ts, run in CI with ENVLESS_TOKEN set to an ev_sk_ API key
import { createClient } from '@goenvless/sdk'
const envless = createClient()
const name = `preview-${process.env.PR_NUMBER}`
await envless.environments.create('api', { name, slug: name })Do not try to feed a CLI device token to the SDK, or an API key to the CLI's write commands, which refuse it with Machine keys are read only. The API keeps the two credential types strictly separate: internal endpoints reject ev_sk_ keys with 401 and error.code auth.unauthorized (the message starts auth.api_key_not_accepted:), and the public API rejects everything else with auth.api_key_required. The runtime sits on the API-key side too: its ENVLESS_TOKEN must be an ev_sk_ machine key, and it refuses anything that is not one.
Every SDK method linked above has a Python twin and a Ruby twin under the same name in snake_case, and a PHP twin under exactly the same name, documented under Python, Ruby and PHP.