The share portal is a static single-page application. It has no backend, no database, and makes no network requests. All plan data is encoded in the URL hash.
Build
bun install
bun run build:portal
Output: apps/portal/dist/
Deploy
Upload the dist/ folder to any static hosting provider.
Nginx
server {
listen 80;
server_name plannotator.internal.example.com;
root /var/www/plannotator;
try_files $uri /index.html;
}
AWS S3 + CloudFront
aws s3 sync apps/portal/dist/ s3://your-bucket/ --delete
Configure the CloudFront distribution to return /index.html for 404s (SPA routing).
Vercel / Netlify / Cloudflare Pages
Point to the repository root:
- Build command:
bun run build:portal - Output directory:
apps/portal/dist
Configure Plannotator
Set the PLANNOTATOR_SHARE_URL environment variable to your portal’s URL:
export PLANNOTATOR_SHARE_URL=https://plannotator.internal.example.com
All share links generated by Plannotator will now point to your self-hosted portal. The import dialog placeholder updates automatically.
When PLANNOTATOR_SHARE_URL is not set, the default https://share.plannotator.ai is used.
Verify
- Start a plan review in Claude Code or OpenCode
- Add an annotation, click Export → Share
- Confirm the share URL starts with your configured domain
- Open the link — the plan and annotations should render correctly