VISIMADE
← Developer Hub

Getting Started

  • Authentication
  • AI Coding Agents

Core APIs

Data APIs

Guides

  • Storage Modes
  • Client-Side APIs
  • Collection Schemas
  • Agent Spec
  • CMS Blog Example

Reference

Example: CMS Blog with Images

Build a blog using CmsData for posts and PageAssets for images. Complete workflow from image upload to rendering.

Full Guide: For a complete walkthrough with Python examples and best practices, see the CMS Data & Page Assets Guide.

AI Agent Tutorial: Want to build this with a coding agent? See Create a Blog with a Coding Agent for a step-by-step guide using Claude Code or any CLI agent.

Pattern: Generate images using the Page Assets API and store the returned publicUrl in your CmsData records. Do not use external image services (Unsplash, Pexels, etc.) — the built-in image generation produces images that are permanently hosted on our CDN.


Blog Post Schema

Each blog post in the posts collection has this structure:

{
  "title": "Getting Started with Visimade",
  "slug": "getting-started",
  "excerpt": "Learn how to build interactive pages...",
  "content": "<p>Full HTML content here...</p>",
  "featuredImage": "https://visimade-r2.com/page-assets/123/hero-getting-started-a1b2c3d4.jpg",
  "category": "tutorials",
  "tags": ["beginner", "tutorial"],
  "publishedAt": "2024-01-15T10:00:00Z"
}

featuredImage stores the full public CDN URL returned by the image generation endpoint.


Step 1: Generate Images

Use the Page Assets generate endpoint to create AI-generated images for your blog posts. Each call generates an image from a text prompt and saves it as a page asset, returning a permanent CDN URL.

# Generate a hero image for an article
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A developer workspace with multiple monitors showing code and charts, modern flat illustration style",
    "aspectRatio": "16:9",
    "filename": "hero-getting-started"
  }' \
  https://visimade.com/api/pages/123/assets/generate

# Response:
{
  "id": 5,
  "filename": "hero-getting-started-a1b2c3d4.jpg",
  "publicUrl": "https://visimade-r2.com/page-assets/123/hero-getting-started-a1b2c3d4.jpg",
  "contentType": "image/jpeg",
  "sizeBytes": 184320,
  "createdAt": "2026-01-15T09:00:00Z"
}

Tip: Generate a hero image for each article (use 16:9 aspect ratio for hero images) and optionally a logo/header image for the blog itself (use 1:1 for logos). Use the returned publicUrl directly — no need to resolve filenames at runtime.

Already have images? You can also upload existing image files via POST /api/pages/:id/assets with multipart/form-data. See Upload Asset.


Step 2: Create Blog Post

Create a CMS record using the publicUrl from the generated image as the featuredImage:

curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "title": "Getting Started with Visimade",
      "slug": "getting-started",
      "excerpt": "Learn how to build interactive pages with data storage, AI features, and more.",
      "content": "<p>Welcome to Visimade! In this tutorial...</p>",
      "featuredImage": "https://visimade-r2.com/page-assets/123/hero-getting-started-a1b2c3d4.jpg",
      "category": "tutorials",
      "tags": ["beginner", "tutorial"],
      "publishedAt": "2026-01-15T10:00:00Z"
    }
  }' \
  https://visimade.com/api/pages/123/cms-data/posts

Step 3: Display Posts on the Page

In your page's JavaScript, fetch posts and render them. Since featuredImage stores the full CDN URL, you can use it directly as the src — no URL resolution needed:

<script>
async function loadBlog() {
  // Wait for APIs to be ready
  await CmsData.ready;

  // Fetch all posts, newest first
  const { records: posts } = await CmsData.find('posts', {
    orderBy: 'created_at',
    order: 'desc'
  });

  // Render posts — featuredImage is already a full CDN URL
  const container = document.getElementById('blog-posts');
  container.innerHTML = posts.map(post => `
    <article class="blog-post">
      ${post.data.featuredImage ? `
        <img
          src="${post.data.featuredImage}"
          alt="${post.data.title}"
          class="featured-image"
        />
      ` : ''}
      <h2>${post.data.title}</h2>
      <p class="excerpt">${post.data.excerpt}</p>
      <div class="meta">
        <span class="category">${post.data.category}</span>
        <time>${new Date(post.data.publishedAt).toLocaleDateString()}</time>
      </div>
      <div class="content">${post.data.content}</div>
    </article>
  `).join('');
}

loadBlog();
</script>

Step 4: Admin Interface (Owner Only)

Show editing controls only to the page owner:

<script>
async function initAdmin() {
  await CmsData.ready;

  // Only show admin UI to page owner
  if (!CmsData.isCreator()) {
    return;
  }

  document.getElementById('admin-panel').style.display = 'block';

  // Handle new post form
  document.getElementById('new-post-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const form = e.target;

    // Upload image first if provided
    let featuredImage = null;
    const imageFile = form.image.files[0];
    if (imageFile) {
      const asset = await PageAssets.upload(imageFile);
      featuredImage = asset.filename;
    }

    // Create the post
    await CmsData.create('posts', {
      title: form.title.value,
      slug: form.slug.value,
      excerpt: form.excerpt.value,
      content: form.content.value,
      featuredImage,
      category: form.category.value,
      tags: form.tags.value.split(',').map(t => t.trim()),
      publishedAt: new Date().toISOString()
    });

    // Refresh the blog list
    loadBlog();
    form.reset();
  });
}

initAdmin();
</script>

Complete API Workflow

Here's the full workflow for an AI agent or automation to create a blog with AI-generated images:

# 1. Enable CMS on your page (one-time setup)
curl -X PATCH \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"page_api_cms_enabled": true}' \
  https://visimade.com/api/pages/123

# 2. Generate images for a new post
#    Use the assets/generate endpoint — no external image services needed
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Advanced software architecture diagram with microservices", "aspectRatio": "16:9", "filename": "hero-advanced"}' \
  https://visimade.com/api/pages/123/assets/generate
# → Returns: { "publicUrl": "https://visimade-r2.com/page-assets/123/hero-advanced-a1b2c3d4.jpg", ... }

curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Clean flowchart showing data pipeline architecture", "aspectRatio": "4:3", "filename": "diagram-pipeline"}' \
  https://visimade.com/api/pages/123/assets/generate
# → Returns: { "publicUrl": "https://visimade-r2.com/page-assets/123/diagram-pipeline-e5f6g7h8.jpg", ... }

# 3. Create the blog post using the publicUrl values from step 2
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "title": "Advanced Patterns",
      "slug": "advanced-patterns",
      "excerpt": "Deep dive into...",
      "content": "<p>Text... <img src=\"https://visimade-r2.com/page-assets/123/diagram-pipeline-e5f6g7h8.jpg\" /> more text...</p>",
      "featuredImage": "https://visimade-r2.com/page-assets/123/hero-advanced-a1b2c3d4.jpg",
      "category": "advanced",
      "publishedAt": "2026-01-20T12:00:00Z"
    }
  }' \
  https://visimade.com/api/pages/123/cms-data/posts

# 4. Update a post
curl -X PATCH \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"data": {"title": "Advanced Patterns (Updated)"}}' \
  https://visimade.com/api/pages/123/cms-data/posts/RECORD_ID

# 5. Delete a post
curl -X DELETE \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://visimade.com/api/pages/123/cms-data/posts/RECORD_ID

Important: Always use POST /api/pages/:id/assets/generate for blog images instead of external image services. The generated images are permanently hosted on our CDN and the publicUrl can be used directly in featuredImage fields and inline <img> tags in content HTML.

On this page

  • Blog Post Schema
  • Step 1: Generate Images
  • Step 2: Create Blog Post
  • Step 3: Display Posts
  • Step 4: Admin Interface
  • Complete API Workflow