Bring your own repo
Move an existing Astro site to Ploy without discovering the publish contract one failed build at a time.
Read this contract before importing code. Ploy validates and patches the tenant repository during publish, so a project that builds on another host can still fail here when its package versions or config shape differ.
Repository contract
package.jsonmust be at the repository root and itsnamemust be exactlyploy-web.astromust be installed as a dependency, not only available globally.astro.config.mjsmust be at the repository root and committed atHEAD.- The config must default-export a plain
defineConfig({...})object literal.
{
"name": "ploy-web",
"dependencies": {
"astro": "^6.0.0",
"@astrojs/cloudflare": "^13.0.0",
"@astrojs/react": "^5.0.0",
"@astrojs/sitemap": "^3.0.0"
}
}If the site uses MDX, also install @astrojs/mdx 5.x.
Match the Ploy toolchain
| Package | Supported major |
|---|---|
astro | 6.x |
@astrojs/cloudflare | 13.x |
@astrojs/react | 5.x |
@astrojs/sitemap | 3.x |
@astrojs/mdx | 5.x, when MDX is used |
Ploy wraps the tenant Astro config with its own config, which imports these packages from the tenant's node_modules. A repository on Astro 7, or one missing the Cloudflare adapter, can therefore fail with an unrelated-looking module not found error.
The sandbox install is a plain bun install. Ploy deletes package-lock.json before installing, so do not rely on npm-only lockfile behavior.
Keep astro.config.mjs patchable
At publish time Ploy AST-patches four fields: site, build.assets, cacheDir, and vite.cacheDir. Each must be represented with plain literals in the exported object. Do not use environment lookups, computed values, helper-returned config, or spread objects for these fields.
import cloudflare from "@astrojs/cloudflare";
import react from "@astrojs/react";
import sitemap from "@astrojs/sitemap";
import { defineConfig } from "astro/config";
export default defineConfig({
site: "https://example.com",
output: "server",
build: { assets: "_astro" },
cacheDir: "./node_modules/.astro",
vite: { cacheDir: "./node_modules/.vite" },
adapter: cloudflare(),
integrations: [react(), sitemap()],
});site must specifically be a string literal. Ploy forces output: "server" for the Workers build even if your local config says otherwise.
Cloudflare Workers runtime
- Every route that should ship as a static page needs
export const prerender = true. - Static assets are served with
html_handling: "drop-trailing-slash". _redirectsentries whose destination ends in/indexor/index.htmlare rejected. Redirect to the public route instead.node:fsis shimmed in the Workers build, including during prerender. Useimport.meta.globfor build-time file discovery instead of filesystem reads.
---
export const prerender = true;
---Recommended migration path
The safest path is to preserve Ploy's starter shell and move the portable parts of your existing site into it:
- Provision a blank Astro site in Ploy and clone its repository through Code Sync.
- Move the existing repository's
src/andpublic/directories into the starter. - Replace framework-specific routes with thin
.astroshells. Addexport const prerender = trueto every route that should be static. - Run
bun install, then test locally withbunx wrangler dev --local. - Push to GitHub
main, run Code Sync, and publish.
# 1. Start from a Ploy-provisioned Astro starter checkout
# 2. Move your existing source and public files into it
cp -R /path/to/existing-repo/src ./src
cp -R /path/to/existing-repo/public ./public
# 3. Add thin .astro route shells and opt static routes into prerendering
# export const prerender = true;
# 4. Install and test with the same runtime shape Ploy uses
bun install
bunx wrangler dev --local
# 5. Connect the repository, sync main, and publish
ploy site code-sync init
ploy site code-sync sync
ploy site publish --waitA public
tenant-starter-astrotemplate andbunx create-ploy-sitecommand are not available yet. Today the exact starter is obtained by provisioning a Ploy site and cloning it through Code Sync, which is included on paid plans.
Import with the CLI
You can seed a new site directly from a compatible local checkout:
ploy site init --from /path/to/repo --waitThis validates the repository contract above and creates a new Ploy site every time. It is an initial import, not an incremental push. Without Code Sync there is no way to send a second commit to the same site, so each code change on the free plan requires another newly seeded site.
Before publishing
- Confirm
package.jsonis namedploy-weband all required packages use the supported majors. - Confirm
astro.config.mjsis committed and uses a plain object literal with patchable literal fields. - Confirm every static route exports
prerender = true. - Replace build-time
node:fslookups withimport.meta.glob. - Test the Workers runtime locally before Code Sync and publish.
Keep going
- Check the complete CLI contract for global options, environment variables, saved state, and exit codes.
- Bring an existing Astro repository into Ploy using the supported toolchain and publish contract.
- Manage workspaces and sites, including domains, routing, publishing, and Code Sync.
- Equip local agents with Ploy skills and inspect a site's design-system context.
- Run the CLI from remote agents and CI with API tokens and non-interactive commands.
