Skip to content

Collections Overview

Collections are BlocksWeb's powerful system for managing structured, reusable data that can be edited in the CMS and referenced in your components.

What Are Collections?

Collections are developer-defined data structures that represent entities in your application like:

  • Blog posts
  • Products
  • Team members
  • Case studies
  • Testimonials
  • Locations
  • Categories
  • Any structured data

Think of collections as typed database tables that you define in your code and your content editors can populate through the CMS.

Why Use Collections?

Without Collections

typescript
// ❌ Hardcoded data in component
const TeamSection = () => {
  const team = [
    { id: 1, name: 'John Doe', role: 'CEO', image: '/john.jpg' },
    { id: 2, name: 'Jane Smith', role: 'CTO', image: '/jane.jpg' }
  ];

  return (
    <div>
      {team.map(member => (
        <div key={member.id}>
          <img src={member.image} alt={member.name} />
          <h3>{member.name}</h3>
          <p>{member.role}</p>
        </div>
      ))}
    </div>
  );
};

Problems:

  • Data hardcoded in component
  • Content editors can't update team members
  • Changes require code deployments
  • No reusability across pages

With Collections

typescript
// ✅ Dynamic data from collection
const TeamSection: IBlockswebComponent = ({ selectedMembers }) => {
  // selectedMembers are full objects, automatically resolved!

  return (
    <div>
      {selectedMembers.map(member => (
        <div key={member.id}>
          <img src={member.image} alt={member.name} />
          <h3>{member.name}</h3>
          <p>{member.role}</p>
        </div>
      ))}
    </div>
  );
};

TeamSection.schema = {
  displayName: 'Team Section',
  options: [
    {
      type: 'collection',
      name: 'selectedMembers',
      label: 'Select Team Members',
      collectionName: 'team',
      multiple: true
    }
  ]
};

Benefits:

  • ✅ Content editors manage team members in CMS
  • ✅ No code changes needed for content updates
  • ✅ Reusable across multiple pages/components
  • ✅ Type-safe with TypeScript
  • ✅ Automatic data resolution

How Collections Work

The Complete Flow

┌─────────────────────────────────────────────────────────┐
│ 1. DEFINE: Developer defines collection structure      │
│    → blocksweb.config.ts                                │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 2. POPULATE: Content editors add records in CMS        │
│    → Products, blog posts, team members, etc.           │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 3. SELECT: Editors select records in component options │
│    → Choose which products to display                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 4. STORE: BlocksWeb stores record IDs                  │
│    → ["product-1", "product-2", "product-3"]           │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 5. RESOLVE: BlocksWeb automatically fetches full data  │
│    → Queries collection provider                        │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 6. RENDER: Component receives full objects as props    │
│    → [{ id: "1", name: "...", price: 99, ... }]        │
└─────────────────────────────────────────────────────────┘

The Magic: Automatic Data Resolution

Zero Boilerplate Data Fetching

When you use the collection option, BlocksWeb handles everything: fetching, resolving IDs, error handling, and caching. Your component simply receives complete data objects as props. No useEffect, no loading states, no API calls to write!

What you define:

typescript
{
  type: 'collection',
  name: 'product',
  collectionName: 'products'
}

What BlocksWeb stores:

typescript
{ product: "product-123" }  // Just the ID

What your component receives:

typescript
{
  product: {
    id: "product-123",
    name: "Awesome Product",
    price: 99.99,
    description: "The best product ever",
    image: "/products/awesome.jpg",
    inStock: true
  }
}

No hooks, no useEffect, no manual fetching!

Collection Types

Single Record Selection

Select one record from a collection:

typescript
const ProductShowcase: IBlockswebComponent = ({ featuredProduct }) => {
  // featuredProduct is a full object
  if (!featuredProduct) return null;

  return (
    <div>
      <h2>{featuredProduct.name}</h2>
      <p>${featuredProduct.price}</p>
      <img src={featuredProduct.image} alt={featuredProduct.name} />
    </div>
  );
};

ProductShowcase.schema = {
  displayName: 'Product Showcase',
  options: [
    {
      type: 'collection',
      name: 'featuredProduct',
      label: 'Featured Product',
      collectionName: 'products',
      multiple: false  // Single selection
    }
  ]
};

Multiple Record Selection

Select multiple records from a collection:

typescript
const ProductGrid: IBlockswebComponent = ({ selectedProducts = [] }) => {
  // selectedProducts is an array of full objects

  return (
    <div className="grid grid-cols-3 gap-4">
      {selectedProducts.map(product => (
        <div key={product.id}>
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>${product.price}</p>
        </div>
      ))}
    </div>
  );
};

ProductGrid.schema = {
  displayName: 'Product Grid',
  options: [
    {
      type: 'collection',
      name: 'selectedProducts',
      label: 'Products',
      collectionName: 'products',
      multiple: true  // Multiple selection
    }
  ]
};

Collection Providers

Collections use providers to fetch and manage data. BlocksWeb includes two built-in providers:

MemoryCollectionProvider

For development and testing - stores data in memory:

typescript
// blocksweb.config.ts
import { MemoryCollectionProvider } from '@blocksweb/core';

export const collectionProvider = new MemoryCollectionProvider({
  data: {
    products: [
      { id: '1', name: 'Product 1', price: 99 },
      { id: '2', name: 'Product 2', price: 149 }
    ]
  }
});

Use cases:

  • Local development
  • Testing
  • Prototyping
  • Demo applications

CMSCollectionProvider

For production - fetches data from BlocksWeb CMS:

typescript
// blocksweb.config.ts
import { CMSCollectionProvider } from '@blocksweb/core';

export const collectionProvider = new CMSCollectionProvider({
  apiUrl: process.env.BLOCKSWEB_API_URL,
  projectId: process.env.BLOCKSWEB_PROJECT_ID
});

Use cases:

  • Production applications
  • Content managed by CMS
  • Multi-user editing
  • Real-time content updates

Custom Providers

You can create custom providers for any data source:

typescript
import { CollectionProvider } from '@blocksweb/core';

class DatabaseCollectionProvider implements CollectionProvider {
  async getRecord(collectionName: string, id: string) {
    // Fetch from your database
    return await db.query('SELECT * FROM ?? WHERE id = ?', [collectionName, id]);
  }

  async getRecords(collectionName: string, ids: string[]) {
    // Fetch multiple records
    return await db.query('SELECT * FROM ?? WHERE id IN (?)', [collectionName, ids]);
  }

  async queryRecords(collectionName: string, query: any) {
    // Custom query logic
    // ...
  }
}

Use cases:

  • Integration with existing database
  • Custom APIs
  • External data sources
  • Legacy system integration

When to Use Collections

✅ Use Collections When:

  • Reusable content: Products, blog posts, team members
  • CMS-managed data: Content editors need to update data
  • Structured data: Data has consistent fields
  • Multi-page usage: Same data appears on multiple pages
  • Dynamic content: Quantity and content changes over time

Examples:

  • E-commerce products
  • Blog posts and articles
  • Team member profiles
  • Customer testimonials
  • Case studies
  • Locations/stores
  • FAQs
  • Portfolio items

❌ Don't Use Collections When:

  • One-off content: Unique page-specific text
  • Static data: Never changes (use component options instead)
  • Highly relational: Complex joins and relationships (use direct API calls)
  • Real-time data: Stock prices, live feeds (use hooks/API calls)

Examples:

  • Hero section title/subtitle (use text options)
  • Button text (use text options)
  • Background colors (use color options)
  • Layout toggles (use checkbox options)

Collections vs Component Options

Understanding when to use each:

Component Options (text, image, color, etc.)

Best for: Component-specific, one-off content

typescript
// ✅ Good use of options
Hero.schema = {
  displayName: 'Hero',
  options: [
    { type: 'text', name: 'title', label: 'Hero Title' },
    { type: 'text', name: 'subtitle', label: 'Subtitle' },
    { type: 'image', name: 'background', label: 'Background Image' }
  ]
};

Collections

Best for: Reusable, structured data

typescript
// ✅ Good use of collections
BlogList.schema = {
  displayName: 'Blog List',
  options: [
    {
      type: 'collection',
      name: 'posts',
      label: 'Blog Posts',
      collectionName: 'blog',
      multiple: true
    }
  ]
};

Real-World Example

Here's a complete example of a product showcase using collections:

typescript
import { IBlockswebComponent } from '@blocksweb/core';

type ProductShowcaseProps = {
  title: string;
  description: string;
  featuredProducts: Array<{
    id: string;
    name: string;
    price: number;
    image: string;
    description: string;
    inStock: boolean;
  }>;
};

const ProductShowcase: IBlockswebComponent<ProductShowcaseProps> = ({
  title,
  description,
  featuredProducts = []
}) => {
  return (
    <section className="py-16">
      <div className="container mx-auto">
        {/* Component-specific content via options */}
        <h2 className="text-4xl font-bold mb-4">{title}</h2>
        <p className="text-lg mb-12">{description}</p>

        {/* Collection data - automatically resolved */}
        <div className="grid grid-cols-3 gap-8">
          {featuredProducts.map(product => (
            <div key={product.id} className="border rounded-lg p-6">
              <img
                src={product.image}
                alt={product.name}
                className="w-full h-48 object-cover mb-4"
              />
              <h3 className="text-xl font-bold mb-2">{product.name}</h3>
              <p className="text-gray-600 mb-4">{product.description}</p>
              <div className="flex items-center justify-between">
                <span className="text-2xl font-bold">${product.price}</span>
                {product.inStock ? (
                  <span className="text-green-600">In Stock</span>
                ) : (
                  <span className="text-red-600">Out of Stock</span>
                )}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

ProductShowcase.schema = {
  displayName: 'Product Showcase',
  category: 'E-commerce',
  options: [
    {
      type: 'text',
      name: 'title',
      label: 'Section Title',
      default: 'Featured Products'
    },
    {
      type: 'text',
      name: 'description',
      label: 'Description',
      default: 'Check out our best-selling products'
    },
    {
      type: 'collection',
      name: 'featuredProducts',
      label: 'Featured Products',
      collectionName: 'products',
      multiple: true  // Allow selecting multiple products
    }
  ]
};

export default ProductShowcase;

Next Steps

Now that you understand collections, learn how to:

Key Takeaways

✅ Collections are developer-defined data structures ✅ Content editors manage collection records in the CMS ✅ Use the collection option type in component schemas ✅ BlocksWeb automatically resolves IDs to full objects ✅ No manual data fetching code needed in components ✅ Choose the right provider: Memory (dev), CMS (prod), or Custom ✅ Use collections for reusable content, options for one-off content