About deploy-to-hosting
deploy-to-hosting is an Agent Skill for deploying existing projects to Hostinger websites. It covers target selection, project-type-specific deployment, build tracking, and Git-based auto-deploy, with distinct procedures for Shared, Cloud, and Agency hosting.
Key features
- 01Select an existing or new target website
- 02Deploy static, Node.js, PHP, and WordPress projects
- 03Track remote Node.js builds
- 04Configure Git branch auto-deployment
- 05Handle Shared, Cloud, and Agency procedures
Use cases
- 01Publish a local project to a Hostinger site
- 02Deploy a repository branch to a website
- 03Diagnose and track a Node.js deployment build
- 04Deploy a WordPress plugin or theme
MCP support
The skill uses Hostinger Connector operations through MCP servers, including separate hosting, wordpress, and agency-hosting servers. It instructs the agent to inspect operation schemas and sequence calls for website setup, deployment, uploads, and build tracking.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ hostinger/ api-mcp-server --skill deploy-to-hostingAfter installing, type/deploy-to-hostingin Claude Code to confirm it loaded.
Details
From the project READMEDeploy to hosting
Ship a project that already exists — on disk or in a Git repository — to a Hostinger website. Building a new site from a prompt (design, store, blog) is the hostinger-headless skill; this one takes code as it is.
Calling the operations
- Read the
inputSchemathatsearchreturns before the first call of each operation. If a name below is rejected as unknown,searchfor what the step does (e.g. "deploy static"). - Batch reads with
multi-execute; values pass between steps as$steps.<i>.<path>. Batches chain operations from one server only — the Hostinger Connector runshosting,wordpressandagency-hostingas separate servers. - Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.
1. Pick the target website
.hostinger/site.jsonin the project: reuse itsdomainandusername(orwebsite_uid).- Otherwise look the domain up with
hosting_websites_list(Shared and Cloud; substring match — take the exact entry) andagency-hosting_websites_list-plan(Agency; keepdetails.uid). - No website yet:
- Shared and Cloud: take a domain (the user's own, or
hosting_domains_generate-free-subdomain) and anorder_idfromhosting_orders_list, thenhosting_websites_create.datacenter_codeis needed only for the first website on a new plan (first entry ofhosting_datacenters_list). Pollhosting_websites_list-setupswithdomainevery 10–15 s untilstatus: completed— uploads and deploys return 404 or 409 before that. - Agency:
agency-hosting_website-setups_createwithflavor: "php-fpm"(plustype: "node-static"for built frontends),settings.php.versionfromagency-hosting_php_list-versions-for-orderanddatacenter_codefromagency-hosting_datacenters_list. Pollagency-hosting_website-setups_statusuntilcompleted; it returns thewebsite_uid.
- Shared and Cloud: take a domain (the user's own, or
- Node.js apps run on Business and Cloud plans (
hostinger_business_*,cloud_*inhosting_orders_list). On other plans, build locally and deploy the output as a static site.
2. Choose the deploy
Shared and Cloud, on the local hostinger-api-mcp server: one operation does every deploy — hosting_deploy with domain and either path (a directory on this machine) or git: { repository: "owner/name", branch }. Given a directory it detects what the project is, packs it itself (it skips node_modules, .git and .env files, listing the .env ones as skippedFiles so you can set their values with hosting_nodejs_replace-environment-variables; it does not read .gitignore, so keep build output out of the directory) and does the upload, build and Git setup. Do not archive the directory or call upload and build operations by hand.
| Project | hosting_deploy treats it as |
|---|---|
| Plain HTML/CSS/JS, or a framework's static build output | static — extracts as-is |
package.json with a build script, a start script or a server dependency (Next.js, Nuxt, Express, Vite…) | nodejs — starts a build, returns buildUuid |
| PHP code, no build step | static |
WordPress plugin folder (a Plugin Name: header) or theme folder (Theme Name: in style.css) | wordpress-plugin / wordpress-theme; activate: true activates a theme |
| A whole WordPress site from elsewhere | wordpress-site, with databaseDump — see the migrate-to-hosting skill |
- Pass
typeto override detection. It is required for an archive (an archive cannot be inspected) and when a directory matches more than one WordPress type, whichhosting_deployrejects as ambiguous. - Node.js build settings come from the project; detection covers the framework and the build script. Set
appType,outputDirectory,buildScript,entryFile,nodeVersion(default 22),rootDirectoryorpackageManagerwhen it is wrong. A TypeScript or other compiled server app always needsentryFileset to the built file (for exampledist/server.js): detection only guesses a root-level file, and the build fails with "Entry file not found". The entry file is relative to the root directory for express, fastify, hono, react-router and other, and to the output directory for nest, nuxt, nitro, astro and svelte-kit (main.js, notdist/main.js); Next.js ignores it. Per-framework values are in the Hostinger docs under Node.js (Supported Frameworks, Build Settings). - Static, PHP, plugin and theme deploys are live when the call returns. A WordPress site import only starts (
status: import_started) and finishes extracting a few minutes later; do not report it complete or verify it before then. Node.js builds run on the server: see step 3.
Agency: the website's type decides (agency-hosting_websites_get): sites created as node-static take agency-hosting_deploy-node-static-website, which runs the build when the project has one; every other site takes agency-hosting_deploy-php-application, which extracts the archive as-is. They deploy archives, synchronously: the site is live when the call returns. WordPress plugins and themes on Agency sites are installed from wp-admin.
Archive rules for Agency (name archives name_YYYYMMDD_HHMMSS.zip) and for archives you build yourself:
- Static: build locally first.
index.htmlmust be at the archive root, not inside a folder:cd dist && zip -r ../site_20260101_120000.zip . - Node.js source: no
node_modules/, no build output (dist/,.next/,build/), no.env*, nothing matched by.gitignore; 50 MB at most.git archive --format=zip -o app_20260101_120000.zip HEADproduces exactly the committed files — mention that uncommitted changes are left out.
hosting_deploy and the agency-hosting_deploy-* operations read files from this machine, so only the local server has them. On the hosted server (mcp.hostinger.com) search does not find them — upload the files yourself as below.
Without the local deploy operations (hosted server; on the local server only for Agency)
- Get upload credentials:
hosting_files_generate-upload-url(username,domain); Agency:agency-hosting_files_generate-upload-url(website_uid). Both returnurl,auth_keyandrest_auth_key, which authenticate the upload instead of the API token. - Upload each file with TUS, where
DESTis its path in the website's storage:
SIZE=$(wc -c < "$FILE" | tr -d ' ')
curl -sS -X POST "$URL/$DEST?override=true" -H "X-Auth: $AUTH_KEY" -H "X-Auth-Rest: $REST_AUTH_KEY" -H "Tus-Resumable: 1.0.0" -H "Upload-Length: $SIZE" -H "Upload-Offset: 0"
curl -sS -X PATCH "$URL/$DEST?override=true" -H "X-Auth: $AUTH_KEY" -H "X-Auth-Rest: $REST_AUTH_KEY" -H "Tus-Resumable: 1.0.0" -H "Content-Type: application/offset+octet-stream" -H "Upload-Offset: 0" --data-binary "@$FILE"
The first call returns 201, the second 204 with an Upload-Offset header equal to the file size. override=true makes a retry safe.
3. Deploy from the uploaded files (RANDOM8 is any fresh 8-character string, e.g. $(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)):
| Project | DEST | Then |
|---|---|---|
| Static or PHP | site.zip | hosting_websites_deploy-static-site-archive with archive_path: "site.zip" |
| Node.js | app.zip | hosting_nodejs_build-settings-from-archive with archive_path: "app.zip", then hosting_nodejs_start-build with those settings, source_type: "archive" and source_options.archive_path: "app.zip" |
| WordPress plugin | every file, as wp-content/plugins/SLUG-RANDOM8/<path in the plugin> | wordpress_plugins_deploy with slug and plugin_path: "SLUG-RANDOM8" |
| WordPress theme | every file, as wp-content/themes/SLUG-RANDOM8/<path in the theme> | wordpress_themes_deploy with slug, theme_path: "SLUG-RANDOM8" and optional is_activated |
| Agency, extracted as-is | .h5g/site.zip | agency-hosting_files_import-website-from-archive with archive_name: "site.zip" |
Agency node-static | .h5g/RANDOM8/app.zip | agency-hosting_websites_build-nodejs-assets with archive_path: ".h5g/RANDOM8" — the directory, not the file |
3. Node.js builds
hosting_deploy returns a buildUuid at once; the build runs on the server and takes minutes. Track it with hosting_get-deployment (domain, optionally buildUuid; the latest build by default), polling every 10–20 s until build.state is completed or failed. It returns the build, its log and the stored build settings; pass the previous logs.lines as fromLine to read only new output.
- Failed build: the response already carries
analysis(fetched once per build; the API allows 5 a minute). - Wrong detection (framework,
output_directory, or an entry file inside the build output, such asdist/server.jsfor a compiled app): deploy again withappType,outputDirectory,buildScriptorentryFileset. An archive deploy sends its settings with the request and leaves the stored ones alone; a Git deploy also stores these values, so later pushes build with them.
On the hosted server, which has no hosting_deploy or hosting_get-deployment:
- After the upload steps below, start the build and track it with
hosting_nodejs_list-buildsandhosting_nodejs_build(byuuid), polling every 10–20 s. - Failed build:
hosting_nodejs_analyse-failed-buildonce per build (5 calls a minute), thenhosting_nodejs_build-logsif the analysis is empty. - Wrong detection: run
hosting_nodejs_start-buildwith explicit values,source_type: "archive"andsource_options.archive_pathset to the archive's file name in the document root (check it is there withhosting_files_list-website-and-directories). Store the same values withhosting_nodejs_update-build-settings. - To review detected settings before any build: upload the archive with
hosting_files_generate-upload-url(the TUS steps are in its description), thenhosting_nodejs_build-settings-from-archive.
4. Auto-deploy from Git
On the local server, hosting_deploy with git: { repository: "owner/name", branch } does all of it: it finds the Git installation that can reach the repository and deploys it as the type you give. Always pass type: "static" or "nodejs" unless the site already runs a Node.js app (or you pass Node.js settings such as appType): a new site is not detected as Node.js, and a Node.js repository deployed as static would publish its source. Then:
- PHP and static sites: saves the auto-deployment settings, which deploys the branch immediately and again on every push;
- Node.js sites: starts a build of the branch with the stored build settings; later pushes build the same way. A Git deploy has nothing to detect from, so when none are stored pass
appTypeandnodeVersion(fromenginesinpackage.json), plusoutputDirectoryandbuildScriptif the app has a build step; they are stored for those pushes.
With no active installation it stops and says so: the user connects GitHub or GitLab once in hPanel under Websites → Manage → Advanced → Git; there is no API for that step. Confirm with hosting_get-deployment, which returns the stored gitAutoDeployment.
On the hosted server, by hand:
hosting_git_list-installations. With noactiveinstallation (checkstatus: "pending"and"suspended"too), the user connects GitHub or GitLab once in hPanel, as above.hosting_git_list-installation-repositorieswith the installationuuidgivesowner,nameanddefault_branch(10 calls a minute).hosting_git_update-auto-deployment-settingswithinstallation_uuid,owner,repository,branchand optionaldirectory.- PHP and static sites deploy the branch immediately and again on every push.
- Node.js sites clone nothing on save: start the first build with
hosting_nodejs_start-build,source_type: "git"and the samesource_options. Later pushes build with the stored settings, so keephosting_nodejs_update-build-settingscorrect.
- Confirm with
hosting_git_auto-deployment-settings.
Agency Plan sites have no Git operations; deploy archives.
5. Environment variables (Node.js)
hosting_nodejs_replace-environment-variables replaces the whole set — anything not sent is deleted.
hosting_nodejs_list-environment-variablesfor the current keys. Values come back as********; never send those back.- Build the full set: every current key plus the new ones, with real values from the project's
.envor the user. When the real value of an existing key is unknown, ask — do not drop it. - Send it once; the app restarts. Values baked in at build time (Next.js
NEXT_PUBLIC_*, ViteVITE_*) need a newhosting_nodejs_start-build.
Keys use uppercase letters, digits and underscores. Never commit .env files or repeat secret values back to the user.
6. Database
- Node.js:
hosting_databases_setup-websitecreates a MySQL database and writesDB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORDandDATABASE_URLinto the environment, then restarts the app. The password is generated and never returned. It fails with 422 when any of those keys exists — remove them first with the step 5 replace. - PHP:
hosting_databases_createwithname,user, a strong generatedpasswordandwebsite_domain; read the prefixed full name back fromhosting_databases_list. The app reads credentials from a config file on the server that stays out of Git, and connects tolocalhostor127.0.0.1. - Agency:
agency-hosting_databases_create-website; the app connects tolocalhost. - The
srvNNNN.hstgr.iohost fromhosting_databases_listis only for connections from outside Hostinger — never put it in the deployed app.
7. Verify and record
curl -s -o /dev/null -w "%{http_code}\n" https://DOMAIN/
curl -s https://DOMAIN/ | grep -o "TEXT THE PROJECT RENDERS"
A 200 alone can be a placeholder page; match real copy. A new site may serve a default page briefly — clear it with hosting_cache_clear-website and retry before calling the deploy failed.
Write .hostinger/site.json in the project so later runs reuse the target: { "domain", "username" or "website_uid", "type": "static" | "nodejs" | "php" }, keeping any fields already there.