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.json must be at the repository root and its name must be exactly ploy-web.
  • astro must be installed as a dependency, not only available globally.
  • astro.config.mjs must be at the repository root and committed at HEAD.
  • The config must default-export a plain defineConfig({...}) object literal.
Minimum package contract
{
  "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

PackageSupported major
astro6.x
@astrojs/cloudflare13.x
@astrojs/react5.x
@astrojs/sitemap3.x
@astrojs/mdx5.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.

Patchable Astro config shape
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".
  • _redirects entries whose destination ends in /index or /index.html are rejected. Redirect to the public route instead.
  • node:fs is shimmed in the Workers build, including during prerender. Use import.meta.glob for build-time file discovery instead of filesystem reads.
Static Astro route
---
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:

  1. Provision a blank Astro site in Ploy and clone its repository through Code Sync.
  2. Move the existing repository's src/ and public/ directories into the starter.
  3. Replace framework-specific routes with thin .astro shells. Add export const prerender = true to every route that should be static.
  4. Run bun install, then test locally with bunx wrangler dev --local.
  5. Push to GitHub main, run Code Sync, and publish.
Recommended repository migration
# 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 --wait

A public tenant-starter-astro template and bunx create-ploy-site command 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:

Seed a new site from a local repository
ploy site init --from /path/to/repo --wait

This 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.json is named ploy-web and all required packages use the supported majors.
  • Confirm astro.config.mjs is committed and uses a plain object literal with patchable literal fields.
  • Confirm every static route exports prerender = true.
  • Replace build-time node:fs lookups with import.meta.glob.
  • Test the Workers runtime locally before Code Sync and publish.

Keep going