← Back to all posts
6 min readCentrali Team

Schemaless Mode: Skip the Schema, Start Building

Create a structure with zero properties and start pushing data immediately. Centrali learns your schema from real records and suggests properties for you.

FeatureTutorial

Most platforms force you to define every field before you can store a single record. Column names, types, constraints — all upfront. That's fine when you know your data model inside out. But what about when you don't?

Maybe you're prototyping a new feature. Maybe you're ingesting data from a third-party webhook and the payload keeps changing. Maybe you just want to move fast and figure out the structure later.

That's what schemaless mode is for.

What Is Schemaless Mode?

Every structure in Centrali has a schema discovery mode. The default is strict — you define properties, and records are validated against them. Schemaless mode flips that on its head: you create a structure with no properties at all, and start writing data immediately. Centrali accepts whatever you send.

Behind the scenes, the system buffers your records and runs inference to learn the shape of your data. It then surfaces schema suggestions — proposed properties with inferred types — that you can accept, reject, or ignore.

Three modes are available:

ModeWhat happens
strictFull validation. Extra fields are rejected.
schemalessNo validation. Any JSON is accepted.
auto-evolvingKnown fields are validated, unknown fields are accepted.

This post focuses on schemaless — the "just let me write data" mode.

Getting Started with the SDK

Install the SDK:

bash
npm install @centrali-io/centrali-sdk

Initialize the client:

typescript
import { CentraliSDK } from '@centrali-io/centrali-sdk'; const client = new CentraliSDK({ workspaceId: 'my-workspace', clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, });

Step 1: Create a Schemaless Structure

Create a structure with schemaDiscoveryMode set to 'schemaless' and no properties:

typescript
const structure = await client.structures.create({ name: 'Incoming Webhooks', slug: 'incoming-webhooks', schemaDiscoveryMode: 'schemaless', });

That's it. No properties array. The structure is ready to accept data.

Step 2: Start Pushing Records

Now write records with whatever shape you need. No schema to worry about:

typescript
// First webhook payload — an order event await client.createRecord('incoming-webhooks', { source: 'shopify', event: 'order.created', orderId: 'ORD-4821', customer: { name: 'Alice', email: 'alice@example.com' }, total: 129.99, currency: 'USD', items: [ { sku: 'WIDGET-A', quantity: 2, price: 49.99 }, { sku: 'GADGET-B', quantity: 1, price: 30.01 }, ], }); // Second webhook — a completely different shape await client.createRecord('incoming-webhooks', { source: 'stripe', event: 'payment.succeeded', paymentId: 'pi_3RBxK2', amount: 12999, currency: 'usd', metadata: { orderId: 'ORD-4821' }, }); // Third — yet another shape await client.createRecord('incoming-webhooks', { source: 'github', event: 'push', repo: 'acme/api', branch: 'main', commits: 3, author: 'bob', });

All three records are accepted and stored. No errors, no validation failures — even though every record has a different shape.

Step 3: Query Your Data

Records are fully queryable from the moment they're created:

typescript
// Get all records from the structure const all = await client.queryRecords('incoming-webhooks'); // Filter by a specific field const shopify = await client.queryRecords('incoming-webhooks', { 'data.source': 'shopify', }); // Get a single record by ID const record = await client.getRecord('incoming-webhooks', recordId);

The data is stored in PostgreSQL JSONB, so queries against nested fields work out of the box.

What Happens Behind the Scenes

While you're writing data, Centrali is quietly doing work in the background:

  1. Buffering — Each record is added to a schema discovery buffer.
  2. Batching — Once enough records accumulate (default: 10), inference kicks in.
  3. Analysis — The system examines the buffered records and detects field names, types, and patterns.
  4. Suggestions — It creates schema suggestions: proposed properties like source (string), total (number), or customer (object).

You can view and act on these suggestions through the API:

typescript
// List pending suggestions const suggestions = await fetch( '/structures/incoming-webhooks/schema-suggestions', { headers: { Authorization: `Bearer ${token}` } } ).then(r => r.json()); // Accept a suggestion (adds the property to the structure) await fetch( `/structures/incoming-webhooks/schema-suggestions/${suggestionId}/accept`, { method: 'POST', headers: { Authorization: `Bearer ${token}` } } );

Once you accept suggestions, those properties become part of the structure. If you later switch the mode to strict, new records will be validated against the accepted schema.

Operational Limits

Schemaless mode isn't a free-for-all. Centrali enforces sensible limits to prevent abuse:

LimitDefault
Max keys per record100
Max nesting depth3 levels
Max string length sampled1,000 characters

These are configurable per structure through the schema discovery settings endpoint.

When to Use Schemaless Mode

Schemaless mode is ideal when:

  • Prototyping — You're exploring a data model and don't want to commit to a schema yet.
  • Ingesting external data — Webhooks, APIs, or imports where the shape varies or evolves.
  • Migration staging — You're moving data from another system and want to land it first, then formalize the schema.
  • Event collection — Capturing events from multiple sources with different payloads.

When your data model stabilizes, switch to auto-evolving (validates known fields, accepts new ones) or strict (full validation). The transition is seamless — existing records aren't affected.

Full Example: Webhook Ingestion Pipeline

Here's a complete example — a webhook endpoint that stores incoming payloads in a schemaless structure:

typescript
import { CentraliSDK } from '@centrali-io/centrali-sdk'; import express from 'express'; const app = express(); app.use(express.json()); const client = new CentraliSDK({ workspaceId: 'my-workspace', clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, }); // One-time setup: create the schemaless structure async function setup() { await client.structures.create({ name: 'Webhook Events', slug: 'webhook-events', schemaDiscoveryMode: 'schemaless', enableVersioning: true, }); } // Accept any webhook payload app.post('/webhooks/:source', async (req, res) => { const record = await client.createRecord('webhook-events', { source: req.params.source, receivedAt: new Date().toISOString(), headers: { contentType: req.headers['content-type'], userAgent: req.headers['user-agent'], }, ...req.body, }); res.status(201).json({ id: record.data.id }); }); app.listen(3001);

After a few days of webhook traffic, check the schema suggestions. Accept the ones that make sense, and you'll have a fully typed structure built from real data — not guesswork.

What's Next

Schemaless mode is one of three schema discovery modes in Centrali. In a future post, we'll cover auto-evolving mode — the middle ground that validates what it knows and learns from what it doesn't.

For now, try schemaless mode the next time you're starting a new project or integrating an external data source. Skip the schema, start building, and let Centrali figure out the rest.

Building something with Centrali and want to share feedback about this feature?

Email feedback@centrali.io