Nextly lets you define a collection in TypeScript or build it in the Visual Schema Builder. This is how that works underneath, and how to decide which path to use for a given piece of content.
One registry, two writers
A collection is a description: a slug, some fields, admin hints, access rules. Where that description came from is not something the rest of the system knows or cares about.
Code-first collections are read from your config at startup. The registry parses your defineCollection() calls, validates them, reconciles the database schema, and generates types.
Schema Builder collections are stored as records and loaded from the database at the same point. Same registry, same validation, same schema reconciliation.
After that step there is no difference. The admin renders both the same way. The data API queries both the same way. A code-first collection can hold a relationship to a visually built one, and neither knows which is which.
The code-first path
import {
checkbox,
defineCollection,
number,
richText,
select,
text,
upload,
} from "nextly/config";
export const Products = defineCollection({
slug: "products",
admin: { useAsTitle: "name" },
fields: [
text({ name: "name", required: true }),
number({ name: "price", required: true }),
richText({ name: "description" }),
upload({ name: "image", relationTo: "media" }),
checkbox({ name: "inStock" }),
select({
name: "availability",
options: [
{ label: "Draft", value: "draft" },
{ label: "Active", value: "active" },
{ label: "Archived", value: "archived" },
],
}),
],
status: true,
});What you get from this path: the schema in code review, a diff when it changes, types generated from it, and the same definition running in every environment because it is a file rather than a row.
One detail worth knowing: a select option is a { label, value } pair, and the value should avoid hyphens. Option values become GraphQL enum names, and hyphens are not legal there. Use in_review, not in-review.
The visual path
The Schema Builder is the same model with a different editor. You add fields in the admin, set them required, pick relationships, and the collection exists as soon as you save it. Nobody has to deploy anything.
It can also export what you built:
// Generated by the Schema Builder export
import {
defineCollection,
relationship,
richText,
select,
text,
upload,
} from "nextly/config";
export const Events = defineCollection({
slug: "events",
admin: { useAsTitle: "name" },
fields: [
text({ name: "name", required: true }),
text({ name: "slug", required: true, unique: true }),
richText({ name: "description" }),
upload({ name: "banner", relationTo: "media" }),
relationship({ name: "speakers", relationTo: "users", hasMany: true }),
select({
name: "stage",
options: [
{ label: "Upcoming", value: "upcoming" },
{ label: "Live", value: "live" },
{ label: "Completed", value: "completed" },
],
}),
],
status: true,
});That is a real file you can commit. The usual reason to is that a collection has settled: it was worth building quickly, and it is now worth reviewing.
Choosing
Neither path is the beginner one.
Reach for code first when the shape is part of your application's contract, when other code depends on the field names, when you want the change to appear in a pull request, or when the collection needs access rules that are genuinely logic rather than a role check.
Reach for the Schema Builder when the shape belongs to the content team rather than to the codebase, when you are still finding out what the fields should be, or when waiting for a deploy is the actual bottleneck.
A useful default: model the things your code reads by name in TypeScript, and let the content team own the rest.
What this does not do
It does not merge the two directions automatically. Exporting a Schema Builder collection to code gives you a file; from then on the file is the source, and you maintain it there. There is no round trip that keeps a code file and a database record in sync, because two writers for one definition is a conflict waiting to happen rather than a feature.
Both paths ship today in 0.0.2-alpha.62. If you find a case where the two produce different behaviour, that is a bug worth reporting.
Start building with Nextly
Free, open source, and yours to own. No sign-up required.