← Back to all posts
(Updated March 2026)6 min readCentrali Team

Schemaless Mode: Skip the Schema, Start Building

Create a collection 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 collection 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 collection 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 centrali = new CentraliSDK({
workspaceId: 'my-workspace',
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
});

Step 1: Create a Schemaless Collection

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

typescript
const structure = await centrali.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 centrali.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 centrali.createRecord('incoming-webhooks', {
source: 'stripe',
event: 'payment.succeeded',
paymentId: 'pi_3RBxK2',
amount: 12999,
currency: 'usd',
metadata: { orderId: 'ORD-4821' },
});
// Third — yet another shape
await centrali.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 centrali.queryRecords('incoming-webhooks');
// Filter by a specific field
const shopify = await centrali.queryRecords('incoming-webhooks', {
'data.source': 'shopify',
});
// Get a single record by ID
const record = await centrali.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 collection. 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 centrali = 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 centrali.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 centrali.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