Publishing and Updating Live Sites with the Netlify CLI: A Guide for Coding Agents
How a coding agent can use the Netlify CLI to publish a built site or artifact to a live URL non-interactively, capture that URL, and push later revisions to the same address.
An agent that writes code can also ship it. The Netlify CLI turns any built directory β a dist/ folder, a single index.html, a static export β into a live URL with one command, and every later revision goes to the same URL with the same command. This guide is written for that workflow: non-interactive, scriptable, and safe to run in a loop.
This guide is CLI-only. For programmatic integration from a backend service, see the Netlify API instead. Commands and flags below were checked against Netlify CLI v26.1.0; run netlify --version and consult the command reference if yours differs.
What βan artifactβ means here
Anything you have already built into static files:
- a framework build output (
dist/,build/,out/) - a single
index.htmland its assets - a static export from any tool
The CLI uploads finished files. It can run your build first, or skip the build entirely and upload a folder as-is β which is the common case for an agent that has already produced the output.
Authenticate without a browser
Interactive login (netlify login) opens a browser, which an agent cannot do. Instead, generate a personal access token once (Netlify UI β User settings β Applications β New access token) and pass it as an environment variable:
export NETLIFY_AUTH_TOKEN=your_token_valueEvery CLI command reads this variable and authenticates silently (Netlify CLI: get started). Store it as a secret in whatever runs the agent; never hard-code it, and scope it as narrowly as your setup allows rather than reusing a full-access personal token across many agents. If youβd rather pass the token per command than set it in the environment, every command also accepts --auth $NETLIFY_AUTH_TOKEN.
Publish a throwaway artifact with no account
For a one-off β an artifact you just want a shareable URL for, with no site to manage and no token β deploy anonymously:
netlify deploy --dir=dist --no-build --allow-anonymous --jsonThis uploads the folder and returns a claimable URL: the site exists without an account. The deploy output includes a site ID and a drop token; a human claims the site within one hour by running netlify claim --site <site-id> --token <drop-token> (or by logging in). Unclaimed sites are removed after one hour, so this is for disposable previews only β use the authenticated flow below for anything youβll revise or keep (deploy command reference).
Create a site to publish to
A deploy needs a target site. Creating one interactively prompts for a team and name β an agent should create it explicitly instead:
netlify sites:create --name my-project --account-slug your-team-slug--name sets the subdomain (my-project.netlify.app); --account-slug picks the team to create it under (sites:create reference). Find your team slug with netlify teams:list or in the Netlify UI under Team settings β note that the CLI calls a team an βaccountβ in some flags (--account-slug here, --team on deploy), so the two names refer to the same thing. If the name is taken, the command reports it β pick another rather than retrying the same one.
Alternatively, create the site and deploy in a single command: netlify deploy accepts --site-name <name> (which implies site creation if it doesnβt exist) together with --team <slug>, so netlify deploy --site-name my-project --team your-team-slug --dir=dist --no-build --prod does both at once.
To reuse an existing site instead of making a new one, link the current folder to it:
netlify linkThis writes a siteId into .netlify/state.json in the project folder, so subsequent deploys from that folder target the linked site with no further flags (Netlify CLI: get started).
In a stateless environment β a fresh container per run, where .netlify/state.json wonβt survive β donβt rely on linking. Capture the site ID once from sites:create --json (the id/site_id field), persist it wherever the agent keeps state, and pass --site <site-id> explicitly on every deploy. That makes each deploy self-contained and independent of the working directory.
First publish
Two things matter to an agent here: which URL you get, and getting the URL back in a form you can parse.
Deploy a prebuilt folder to a draft URL (a unique, unlisted preview address):
netlify deploy --dir=dist --no-build --json--dir=distuploads that folder as the site.--no-buildskips the build β use it when you have already built the output, which is the usual agent case (deploy command reference).--jsonprints the deploy result as JSON so you can read the URL programmatically instead of scraping human-readable stdout.
The JSON includes deploy_url (the draft address) and, on a production deploy, url (the live production address). Parse that field rather than regexing the console text.
Promote the same output to the production URL with --prod:
netlify deploy --dir=dist --no-build --prod --jsonThis publishes to the siteβs stable production URL (my-project.netlify.app or a custom domain). The draft-first flow is useful when you want to verify a build at its preview URL before promoting it; if you just want it live, deploy straight to --prod.
If you do want Netlify to run the build for you (it has a repo or a build command configured), drop --no-build and the CLI runs the build before uploading.
Capture the live URL
Because --json emits structured output, an agent reads the URL directly:
URL=$(netlify deploy --dir=dist --no-build --prod --json | jq -r '.url // .deploy_url')echo "Live at: $URL"The .url // .deploy_url fallback keeps this robust across deploy types: deploy_url (the draft address) is always present, while url (the production address) appears on a production deploy. Hand $URL back to the user. Do not parse the decorated stdout banner β it is formatted for humans and its layout is not a stable contract.
The revision loop: updating a published site
This is the part an agent repeats. To ship a revision, rebuild your output and run the same deploy command against the same site:
# after editing and rebuilding into dist/netlify deploy --dir=dist --no-build --prod --jsonThree properties make this safe to run repeatedly:
- The production URL is stable. Every production deploy replaces whatβs live at the same address. The userβs link never changes across revisions.
- Deploys are atomic and immutable. Each deploy is a complete, versioned snapshot. A new deploy goes live all at once; it does not patch files in place. Nothing serves a half-updated site mid-deploy.
- Rollback is instant. Because every deploy is retained as an immutable snapshot, you can revert to any previous one from the Netlify UI (Deploys β select a deploy β Publish deploy) without rebuilding. An agent that ships a bad revision can tell the user exactly how to roll back.
So the mental model is: build β deploy β same URL, new immutable snapshot. Repeat as many times as the task needs.
One cost note for loops: on credit-based plans each production deploy costs 15 credits, but draft and preview deploys are free (how credits work). So an agent iterating rapidly should deploy to draft URLs (omit --prod, see the next section) and promote to production only when a revision is ready β that keeps iteration free and avoids burning credits on every intermediate build.
Preview a revision before it goes live
When you want the user to review a change without touching the production URL, deploy to a named draft instead of production:
netlify deploy --dir=dist --no-build --alias=review-42 --json--alias fixes the subdomain of the draft URL, so you get a predictable preview address (useful when you want the same review link across a few iterations) rather than a random one (deploy command reference). Alias strings are capped at 37 characters. Omit --prod and the production URL stays untouched until you promote a build to it.
A complete agent recipe
End to end, from nothing to a live URL and one revision:
# 0. Auth (once, from a stored secret)export NETLIFY_AUTH_TOKEN=your_token_value
# 1. Create the site (once)netlify sites:create --name my-project --account-slug your-team-slug
# 2. Link this folder to it so later deploys need no --site flagnetlify link --name my-project
# 3. Build your output however the project builds (agent's job)npm run build # produces dist/
# 4. Publish to production and capture the URLURL=$(netlify deploy --dir=dist --no-build --prod --json | jq -r '.url')echo "Live at: $URL"
# 5. Later: edit, rebuild, redeploy β same command, same URLnpm run buildnetlify deploy --dir=dist --no-build --prod --json | jq -r '.url'For a single HTML file with no build step, point --dir at the folder that contains it and skip step 3 entirely.
Common gotchas for automated runs
Deploying the wrong folder. --dir must point at the finished output (dist, build, out), not the project root. Uploading the root serves your source files, not your site.
Forgetting --no-build on a prebuilt folder. Without it, the CLI may try to run a build command. If the output is already built, --no-build keeps the deploy fast and predictable.
Relying on the interactive prompt. A first netlify deploy with no linked site prompts the human to pick or create one β which stalls an agent. Create the site with sites:create and link it (or pass --site) so nothing ever blocks on input.
Parsing stdout instead of --json. The human-readable output is not a stable interface. Always add --json and read the field you need.
Missing NETLIFY_AUTH_TOKEN. Without the token in the environment, commands fall back to interactive login and hang. Confirm the variable is set before the first command.