BotCreds Agent Artifacts gives AI agents a permanent home for their outputs.
Post a single HTML file to the REST API. The plugin parses it, extracts scripts and styles, saves them as static files, enqueues them properly via WordPress APIs, and serves the result at a clean public URL with strict security headers. No build tools. No infrastructure. One API call.
/wp-json/wp/v2/artifacts<script> and <style> blocks and saves them as static files in wp-content/uploads/artifacts/{id}/wp_enqueue_script() / wp_enqueue_style() — no inline scripts in rendered outputwp_kses() and an expanded allowed-tags list (canvas, SVG, inputs, video, audio, data-* attributes)yourdomain.com/artifacts/{slug}/ with a strict Content Security PolicyFrom the caller’s perspective: POST HTML, get URL. Everything else happens server-side.
Deployment
/wp-json/wp/v2/artifacts/{id} and the public URL stays the sameartifact_description field for internal documentation<meta> tags and other <head> elements from submitted HTML are preserved in output<script src="..."> tags are registered as external dependencies and enqueued alongside local assetsfilemtime() so browsers fetch updates automaticallySecurity
cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, esm.sh, cdn.skypack.dev — scripts and styles load from these without any configuration<!-- artifact:fetch https://api.example.com -->) or a deploy-time meta field; the plugin adds them to connect-src automaticallyX-Content-Type-Options, X-Frame-Options, Referrer-Policyartifact capabilities are separate from standard post capabilities; only Administrators can create artifacts by defaultDeveloper Hooks
botcreds_agent_artifacts_csp — filter the full CSP header value for any artifactbotcreds_agent_artifacts_allowed_html — filter the wp_kses allowed-tags arraybotcreds_agent_artifacts_grant_to_role() — helper to grant capabilities to additional rolessingle-artifact.php in your active theme to replace the render templateOpenClaw (AI personal assistant)
OpenClaw agents can deploy interactive dashboards, daily digests, data visualizations, and mini-apps in a single tool call. Generate the HTML, POST it, get the URL — no manual steps, no context switching.
For artifacts that fetch live data, add a pragma comment to the HTML and the CSP is updated automatically:
<!-- artifact:fetch https://api.openweathermap.org -->
For recurring reports (daily digests, weekly summaries), store the artifact ID after the first deploy and update in place on subsequent runs. The URL never changes.
Claude Code (terminal-based coding agent)
Claude Code sessions can invoke a shell deploy script directly after generating output. Add a scripts/deploy-artifact.sh to your project and reference it in your CLAUDE.md — Claude will use it to ship outputs without leaving the terminal. No manual copy-paste, no browser switching.
Codex (OpenAI coding agent)
Same pattern as Claude Code. Add deployment instructions to your AGENTS.md and Codex can write HTML, call the deploy script, and report the live URL — all in one agent run.
GitHub Actions (versioned project)
For projects that build a static HTML output — dashboards, reports, documentation, changelogs — a GitHub Actions workflow can deploy to an artifact on every push to main. The artifact ID is stored as a repository variable so the public URL stays stable across all future deploys. Push build deploy done.
curl -X POST "https://your-site.com/wp-json/wp/v2/artifacts" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"title": "My App",
"status": "publish",
"meta": {
"artifact_html": "<!DOCTYPE html><html><body><h1>Hello.</h1></body></html>",
"artifact_description": "Built by my AI agent"
}
}'
The response link field is the public URL of the deployed artifact.
Include the fetch pragma in your HTML — no configuration needed:
<!-- artifact:fetch https://api.openweathermap.org -->
<!DOCTYPE html>
<html>
<body>
<div id="weather"></div>
<script>
fetch('https://api.openweathermap.org/data/2.5/weather?q=Denver&appid=YOUR_KEY')
.then(r => r.json())
.then(d => document.getElementById('weather').textContent = d.weather[0].description);
</script>
</body>
</html>
name: Deploy Artifact
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: Deploy to Artifact
env:
WP_SITE: ${{ secrets.ARTIFACT_WP_SITE }}
WP_USER: ${{ secrets.ARTIFACT_WP_USER }}
WP_PASS: ${{ secrets.ARTIFACT_WP_PASS }}
ARTIFACT_ID: ${{ vars.ARTIFACT_ID }}
run: |
PAYLOAD=$(jq -n --arg title "My Dashboard" --rawfile html dist/index.html \
'{title: $title, status: "publish", meta: {artifact_html: $html}}')
ENDPOINT="$WP_SITE/wp-json/wp/v2/artifacts"
[ -n "$ARTIFACT_ID" ] && ENDPOINT="$ENDPOINT/$ARTIFACT_ID"
curl -sf -X POST "$ENDPOINT" -u "$WP_USER:$WP_PASS" \
-H "Content-Type: application/json" -d "$PAYLOAD" | jq -r '.link'