Skip to main content

Publishing

The elven publish command takes a companion project directory and ships it: builds, uploads, syncs the listing. One command from edit to live.

What it does

From a project directory containing .elven.json:

  1. Build. Runs the project's build (default: node build.cjs if present, else npm run build). Skip with --no-build.
  2. Create the listing on first publish. If .elven.json has no listingId, the CLI creates a draft listing and writes the id back to .elven.json.
  3. Upload the bundle. Every file in build.outputDir is uploaded to the Elven CDN under c/{your-slug}/apps/{listingId}/.... Skip with --no-deploy.
  4. Flip the listing live. Updates the listing to status=active with a fresh manifestUrl pointing at the uploaded bundle.

After this, your companion is installable from https://app.elvenvtt.com/c/{your-slug}.

.elven.json schema

{
"type": "app", // "app" | "pack" | "template"
"name": "My Companion", // Display name on the storefront
"description": "Short tagline shown on listing cards.",
"listingId": "auto-filled-on-first-publish",
"build": {
"outputDir": "dist", // Required — what to upload
"command": "node build.cjs", // Optional — default chosen automatically
"manifestFile": "marketplace.json",// Optional — file that becomes the manifestUrl
"thumbnailFile": "assets/cover.webp" // Optional — auto-set on the listing
}
}

Minimal example:

{
"type": "app",
"name": "My Companion",
"build": { "outputDir": "dist" }
}

Don't commit credentials to .elven.json. The CLI never reads or writes tokens through it.

Usage

cd my-companion/
elven publish

# Skip the build step (use existing dist/ contents):
elven publish --no-build

# Only sync metadata, skip file upload:
elven publish --no-deploy

# Run from a different directory:
elven publish -C ./packages/my-companion

What "type" means

TypeWhat it is
appInteractive companion (a React/HTML/JS bundle that loads in Elven's panel iframes). Most common.
packContent bundle — recipes, tokens, maps, asset libraries.
templateActor template — a prefab actor with components configured.

Pricing

Free listings (default) work immediately. Paid listings (--price <cents> on listing create, or set later) require Stripe Connect onboarding — see elven seller onboard.

GitHub Actions

Auto-publish on every push to main.

  1. Generate a PAT at app.elvenvtt.com/developerCLI TokensNew Token. Save the value.
  2. In your companion's repo: Settings → Secrets and variables → Actions → New repository secret. Name ELVEN_TOKEN, paste the value.
  3. Add .github/workflows/publish.yml:
name: Publish companion

on:
push:
branches: [main]
workflow_dispatch: {}

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Publish to Elven
env:
ELVEN_TOKEN: ${{ secrets.ELVEN_TOKEN }}
run: npx @elvenvtt/cli publish

Push to main. The Action installs the CLI, builds your companion, and ships it. The first run creates the listing; subsequent runs update in place.

CI hardening

  • The CLI uses ELVEN_TOKEN from env when no profile is configured. No elven login needed.
  • Exit codes are stable — branch on them in shell wrappers. See Exit Codes.
  • Pass --json (or pipe to anything) for structured output. The CLI auto-switches to JSON when stdout isn't a TTY.