The @localess/cli package is the official command-line interface for the Localess headless CMS platform. It provides commands to authenticate with your Localess instance, synchronize translations, generate TypeScript type definitions, and sync code-defined schemas with your space.
- Node.js >= 24.0.0
# Install as a project dev dependency (recommended)
npm install @localess/cli -D
# Or install globally
npm install @localess/cli -g- 🔐 Authentication — Secure credential storage for CLI and CI/CD environments
- 🌐 Translations — Push, pull, and diff translation files against your Localess space
- 🛡️ Type Generation — Generate TypeScript type definitions from your Localess content schemas for end-to-end type safety
- 🧬 Schema Sync — Define schemas in code and pull, diff, and push them against your Localess space
Authenticate with your Localess instance. Credentials are validated immediately and stored securely in .localess/credentials.json with restricted file permissions (0600).
localess login --origin <origin> --space <space_id> --token <api_token>If any option is omitted, the CLI will interactively prompt for the missing values. Credentials are validated with a GET /spaces/{spaceId} call before being saved, and .localess is appended to .gitignore automatically (the file is created if absent; the entry is skipped if already present). If a session already exists (env vars or file), login prints Already logged in. and exits without prompting — run localess logout first to switch credentials.
Options:
| Flag | Description |
|---|---|
-o, --origin <origin> |
Localess instance URL (e.g., https://my-localess.web.app) |
-s, --space <space> |
Space ID (found in Localess Space settings) |
-t, --token <token> |
API token (input is masked for security) |
-v, --verbose |
Print verbose debug output |
Examples:
# Interactive login (prompts for any missing values)
localess login
# Non-interactive login (CI/CD)
localess login --origin https://my-localess.web.app --space MY_SPACE_ID --token MY_API_TOKENFor CI/CD pipelines, you can provide credentials through environment variables instead of running localess login. The CLI automatically reads these variables and skips the file-based credentials. All three must be set — if any is missing, the CLI falls back to .localess/credentials.json in the current working directory:
export LOCALESS_ORIGIN=https://my-localess.web.app
export LOCALESS_SPACE=MY_SPACE_ID
export LOCALESS_TOKEN=MY_API_TOKEN
localess translation pull en --path ./public/locales/en.json| Variable | Description |
|---|---|
LOCALESS_ORIGIN |
Localess instance URL |
LOCALESS_SPACE |
Space ID |
LOCALESS_TOKEN |
API token |
Clear stored credentials from .localess/credentials.json (the file is overwritten with {}).
localess logoutIf you authenticated via environment variables, those must be unset manually —
logoutonly affects file-based credentials.
localess translationwas previouslylocaless translations, andlocaless typewaslocaless types. The old plural names still work as aliases for backward compatibility.
Upload a local JSON translation file to Localess. Prints only the keys the selected --type acts on, as +/~/- lines under a summary such as Added 1 translation in locale "en". or, with --dry-run, Dry run: would add 1 translation in locale "en": (No translations to add for locale "en". when there is nothing to do). It does not print a full diff; use translation diff for that. update-existing, delete-missing-key and delete-missing-value first ask the server for a dry run, list the affected keys and prompt for confirmation (skippable with -y, --yes, automatically skipped under --dry-run, and nothing is pushed when the dry run reports no keys); add-missing never prompts since it is additive-only. Declining the prompt prints Aborted. and exits 1.
The server responds with { message, ids, dryRun? }, where ids lists only the keys the push type wrote (or would write).
localess translation push <locale> --path <file> [options]Arguments:
| Argument | Description |
|---|---|
<locale> |
ISO 639-1 locale code (e.g., en, de, fr) |
Options:
| Flag | Default | Description |
|---|---|---|
-p, --path <path> |
(required) | Path to the JSON translations file |
-f, --format <format> |
flat |
File format: flat or nested |
-t, --type <type> |
add-missing |
Update strategy: add-missing, update-existing, delete-missing-key, or delete-missing-value |
--dry-run |
false |
Preview changes without applying them (also skips the confirmation prompt) |
-y, --yes |
false |
Skip the confirmation prompt for update-existing/delete-missing-key/delete-missing-value |
-v, --verbose |
false |
Print verbose debug output |
Update Strategies:
| Type | Description | Confirmation |
|---|---|---|
add-missing |
Adds translations for keys that do not yet exist in Localess | Never — safe for unattended CI |
update-existing |
Updates translations for keys that already exist in Localess — overwrites any edits made in Localess since your last pull | Prompted (unless -y/--dry-run, or nothing differs) |
delete-missing-key |
Deletes translations for keys absent from the local file in every locale — run it with a complete file (normally the source locale) | Prompted (unless -y/--dry-run, or nothing is stale) |
delete-missing-value |
Removes only <locale>'s value of keys absent from the local file; other locales keep theirs |
Prompted (unless -y/--dry-run, or nothing is stale) |
File Formats:
-
flat— A flat JSON object where keys may use dot notation:{ "common.submit": "Submit", "nav.home": "Home" } -
nested— A nested JSON object that is automatically flattened before uploading:{ "common": { "submit": "Submit" }, "nav": { "home": "Home" } }
Examples:
# Push English translations (add missing keys only)
localess translation push en --path ./locales/en.json
# Push with update-existing strategy (prompts for confirmation)
localess translation push en --path ./locales/en.json --type update-existing
# Same, but skip the confirmation prompt (e.g. scripted/CI use)
localess translation push en --path ./locales/en.json --type update-existing --yes
# Delete keys absent from the source file, in every locale (prompts for confirmation)
localess translation push en --path ./locales/en.json --type delete-missing-key
# Remove only the German values of keys absent from de.json (prompts for confirmation)
localess translation push de --path ./locales/de.json --type delete-missing-value
# Preview changes without applying (dry run)
localess translation push en --path ./locales/en.json --dry-run
# Push nested-format translations
localess translation push de --path ./locales/de.json --format nestedPull translations from your Localess space and save them to a local file. Keys are sorted alphabetically (recursively for nested) so the output is stable across runs and diff-friendly in git. In nested, a key that is also the parent of other keys (button beside button.save) keeps its child keys; its own value is left out and listed in a warning. Use flat to keep every key.
localess translation pull <locale> --path <file> [options]Arguments:
| Argument | Description |
|---|---|
<locale> |
ISO 639-1 locale code (e.g., en, de, fr) |
Options:
| Flag | Default | Description |
|---|---|---|
-p, --path <path> |
(required) | Output file path |
-f, --format <format> |
flat |
File format: flat or nested |
--draft |
false |
Pull the draft (unpublished) version |
--raw |
false |
Pull only the values stored for the locale, without fallback filling — for files you edit and push back. Cannot be combined with --draft |
-v, --verbose |
false |
Print verbose debug output |
Examples:
# Pull English translations as flat JSON
localess translation pull en --path ./locales/en.json
# Pull German translations as nested JSON
localess translation pull de --path ./locales/de.json --format nested
# Pull draft (unpublished) translations
localess translation pull en --path ./locales/en.json --draftRead-only comparison between a local translations file and your Localess space — published by default, the draft with --draft, or the values stored for the locale with --raw (exactly what push acts on). Groups keys into Only in file/Different/Only in Localess sections (color-coded, git-diff style +/~/- symbols); unchanged keys collapse into a single count by default so drift stays visible even with thousands of translations. Exits 1 if anything differs (useful as a CI drift gate), 0 when everything is unchanged. Does not modify anything — use push to apply changes.
localess translation diff <locale> --path <file> [options]Arguments:
| Argument | Description |
|---|---|
<locale> |
ISO 639-1 locale code (e.g., en, de, fr) |
Options:
| Flag | Default | Description |
|---|---|---|
-p, --path <path> |
(required) | Path to the local translations file |
-f, --format <format> |
flat |
File format: flat or nested |
--draft |
false |
Compare against the draft version |
--raw |
false |
Compare against stored values, without fallback filling — what push acts on. Cannot be combined with --draft |
-a, --all |
false |
Also print unchanged keys |
-v, --verbose |
false |
Print verbose debug output |
Examples:
# Compare a local file against the space (exits 1 on drift)
localess translation diff en --path ./locales/en.json
# Also list unchanged keys
localess translation diff en --path ./locales/en.json --all
# Compare against stored values — what push would act on
localess translation diff de --path ./locales/de.json --raw
# CI drift gate
- run: localess translation diff en --path ./locales/en.jsonA locale the space doesn't have is refused (checked when the token can read the space).
The translation commands are stateless building blocks: each has a fixed direction, and you choose the direction per locale and per step.
Source (pull, diff) |
Flag |
|---|---|
| published, fallback-filled | (default) |
| draft, fallback-filled | --draft |
| stored values, no fallback filling | --raw |
pullreplaces the file with Localess's version — Localess wins.push --type …applies one operation from the file — the file wins:add-missing,update-existing,delete-missing-key(every locale),delete-missing-value(this locale only).diffshowsOnly in file/Different/Only in Localessagainst the same sourcepullwould use and exits 1 on drift;diff --rawpredicts exactly whatpushwould act on.
Typical setups:
- Code owns everything:
push --type add-missingandupdate-existingper locale;diff --rawas a CI gate. - Localess owns everything:
pullper locale at build time; translators edit in Localess. - Code owns keys and the source language, translators own the rest:
push en --type add-missing/update-existing/delete-missing-keyfrom the source file;pull <locale>for the other locales.
Fetch your space's schema definitions from Localess and generate TypeScript type definitions. The output file provides full type safety when working with Localess content in your TypeScript projects: one interface per ROOT/NODE schema (with _id and a literal _schema discriminator), a string-literal union per ENUM schema, the ContentAsset/ContentLink/ContentReference/ContentRichText helper types, and a ContentData union of all ROOT schemas.
localess type generate [--path <output_path>] [--prefix <prefix>]Options:
| Flag | Default | Description |
|---|---|---|
-p, --path <path> |
.localess/localess.d.ts |
Path to write the generated TypeScript definitions file |
--prefix <prefix> |
'' |
Prefix to prepend to all generated type names |
-v, --verbose |
false |
Print verbose debug output |
Note: Your API token must have Development Tools permission enabled in Localess Space settings.
Example:
# Generate types to the default location
localess type generate
# Generate types to a custom path
localess type generate --path src/types/localess.d.ts
# Prefix all generated type names (e.g. PascalCase namespacing to avoid collisions)
localess type generate --prefix Localess
# produces `LocalessPage`, `LocalessHeroBlock`, `LocalessContentAsset`, etc.Using generated types:
import type { Page, HeroBlock } from './.localess/localess';
import { getLocalessClient } from "@localess/react";
const client = getLocalessClient();
const content = await client.getContentBySlug<Page>('home', { locale: 'en' });
// content.data is now fully typed as PageDefine Localess schemas in TypeScript with @localess/schema (defineSchema/defineEnum/defineConfig) and sync them bidirectionally with your Localess space. Entry-file convention: a TS/JS file exporting the result of defineConfig() — by convention schemas/index.ts, but any path works.
Prerequisite: Your API token must have the Development Tools permission (
DEV_TOOLS) — same astype generate. No dedicated schema permission exists.
Offline — no login, no network call. Runs @localess/schema's validate() against the entry's config and prints each issue (ERROR/WARNING, code, path, message). Exits 1 when any error-severity issue is present; 0 otherwise (warnings alone don't fail).
localess schema validate ./schemas/index.ts
localess schema validate ./schemas/index.ts --format json # machine-readable, for CIFetches the space's schemas and (re)generates one TypeScript definition file per schema (kebab-case file name, e.g. HeroBlock → hero-block.ts) plus index.ts (a defineConfig() call) into --path (default schemas). Repeatable and safe to re-run — only ever overwrites/deletes files it previously generated (marked with a header comment); a same-named hand-written file without that marker is skipped and reported, and never overwritten.
localess schema pull
localess schema pull --path src/schemas
localess schema pull --print-width 100 # match your own .prettierrc printWidth- Every field is emitted wrapped in
defineField(...)(imported from@localess/schemaalongsidedefineSchema), not as a bare object literal, so hand-edits to a pulled file still getdefineField's excess-property checking. Schemas with no fields import onlydefineSchema;ENUMschemas usedefineEnum. - Cross-schema references (
OPTION/OPTIONSsource,SCHEMA/SCHEMASschemas) that point at another pulled schema becomeimport { X } from './x'statements and by-value refs; ids not present in the pulled set stay plain strings. - A
defineField(...)call wraps to one property per line once it would exceed--print-widthcolumns (default80, Prettier's default) — set it to match your project'sprintWidth. - Output is deterministic — the same server state (and
--print-width) produces byte-identical files.
Read-only comparison between the entry's code-defined schemas and the space — same grouped/colored report layout as translation diff, with schema-specific Create/Update/Stale sections (unchanged schemas collapsed into a count by default). Exits 1 if anything differs (CI drift gate), 0 when everything is unchanged.
localess schema diff ./schemas/index.ts
localess schema diff ./schemas/index.ts --all # also list unchanged schemasValidates, diffs, then pushes the entry's schemas to the space. The pre-push diff uses the same grouped/colored report as schema diff (-a, --all to also list unchanged schemas).
localess schema push ./schemas/index.ts --dry-run # preview only
localess schema push ./schemas/index.ts # upsert: create/update, never delete
localess schema push ./schemas/index.ts --delete # sync: also delete schemas absent from code
localess schema push ./schemas/index.ts --delete -y # sync, skip the deletion confirmation prompt- Aborts (exit
1) without pushing ifvalidate()reports any error (warnings are printed but don't block). - Default mode is upsert — creates and updates, never deletes. Schemas on the server but absent from code are reported as
staleand left alone. --deleteswitches to sync mode, which also deletes stale schemas. Without--yes, you're asked to confirm the exact list before anything is deleted;--dry-runskips the prompt (nothing is written either way), and no prompt is shown when nothing is stale.- Prints the server's final counts:
created,updated,deleted,unchanged(prefixed with[DryRun]under--dry-run). - Reconciles the pre-push diff against the server's returned ids and prints a
⚠ Prediction mismatchwarning (without failing) for any schema whose predicted status differs from the server's actual outcome — e.g. a concurrent change made between the preview and the push.staleentries are only checked in sync mode, since upsert never sends them.
| File | Description |
|---|---|
.localess/credentials.json |
Stored login credentials (created by localess login, mode 0600) |
.localess/localess.d.ts |
Generated TypeScript definitions (created by localess type generate) |
schemas/*.ts, schemas/index.ts |
Generated schema definitions (created by localess schema pull; path via --path) |
localess loginappends.localessto.gitignoreautomatically. If you want to commit generated types, refine that entry to.localess/credentials.jsonafterwards.
The CLI and the Localess platform are released in lockstep. Before each command that talks to the platform, the CLI reads <origin>/assets/version.json and blocks the command (exit 1, before any API call) if the platform's major version differs from the CLI's or the platform is below the command's minimum (currently 4.1.0 for every platform-facing command). login, logout, and schema validate are exempt. The check is skipped silently when no credentials are configured or the version can't be determined (non-2xx, unreachable, malformed, or a 3-second timeout). Set LOCALESS_SKIP_VERSION_CHECK to any value to bypass it.
Every command checks the npm registry for a newer @localess/cli version (3-second timeout, failures ignored silently) and, after the command finishes, prints an "Update available" box with the npm install --save-dev @localess/cli@<tag> command when one exists. Pre-release (-dev.*) installs also check the dev dist-tag; a newer stable release always wins over a newer dev build.
Any non-OK response from the Localess API is rendered as a boxed error (Status, optional Code, redacted URL, and a Hint) and the command exits 1. 401 and 403 hints link directly to your space's token settings page and, for 403, list the permission(s) the token is missing. Network errors and 5xx responses are retried 3 times with a 500 ms delay before failing.
This package ships a SKILL.md file that provides AI coding agents (GitHub Copilot, Claude Code, Cursor, and others) with accurate, up-to-date APIs, patterns, and best practices. Most agents automatically read SKILL.md when starting a session.
SKILL.md is included in the npm package, so it is available locally after installation. Reference it from your project's AGENTS.md to ensure your agent reads accurate Localess documentation every session:
## Localess
@node_modules/@localess/cli/SKILL.mdThe @ prefix is the syntax used by most agent tools (GitHub Copilot, Claude Code, Cursor) to import file contents inline into the agent context.
When you change the public API of this package, update SKILL.md alongside your code:
- New option or parameter → add it to the relevant options table and usage example
- Changed behaviour → update the description and any affected code snippets
- Deprecated API → mark it clearly and point to the replacement
- New command or subcommand → add a full entry with all flags and examples