Dynamic Collections
What the Visual Schema Builder creates, and how it coexists with collections defined in code.
A collection defined in nextly.config.ts exists because you wrote it. A collection built
in the Visual Schema Builder exists because someone created it in
the admin, and its definition lives in the database. The second kind is a dynamic
collection.
Both produce the same thing downstream: real database tables, the same REST and Direct API, the same admin screens, the same access control. The difference is where the definition is stored and who can change it.
Availability
| Built in | Created through the Visual Schema Builder at /admin/builder/collections |
| Permission | Creating and editing schema is an admin capability, separate from editing content |
Which to use
| Use code-first when | Use the Schema Builder when |
|---|---|
| The shape is part of your application's logic | The shape is part of the site's content |
| You want it reviewed in a pull request | You want a non-developer to add a field without a deploy |
| It ships with your repository | It differs between environments, or is created by a client |
The two coexist. A project can define posts in code and let an editor add a testimonials
collection in the admin, and nothing about either changes because the other exists.
Moving between them
A collection built in the admin can be moved into code with nextly db:sync --promote <slug>,
without recreating the table or losing content; only its slug and fields are carried over
(Collections). The other direction is not supported: a
code-first collection that has been synced shows read-only in the Schema Builder.
What differs from a code-first collection
- Its runtime definition lives in a database row. The generated migration, however, carries both the table DDL and a serialized copy of the field definition, so once you commit it the shape is in git and is reviewable in a pull request.
- A metadata-only edit — renaming a label, changing help text — emits no SQL migration,
because nothing about the table changed. In development the builder also mirrors the entity
into a committable
ui-schema.json, so such an edit usually still leaves something to review. That mirror is best-effort and development-only: treat the absence of a migration as the reliable signal, and the manifest as a convenience rather than a guarantee. - The trade is narrower than "no oversight": structural changes arrive as a committed migration you can review, and presentational ones usually arrive in the development manifest.
- It does generate a SQL migration file under your
migrationsDir, and that file is what recreates the table and its registry row in another environment. Commit it. Deploying without it leaves the collection unreproducible outside the database it was created in. - A many-to-many relationship field stores its links in a junction table of its own, and the
migration follows that table through the field's life: adding the field creates it, removing
the field drops it (a field added later under the same name starts with no links), renaming
the field — or changing its
junctionTable— renames it, indexes and constraints included, so the links travel with the name; pointing the field at another collection replaces its table, and dropping the collection drops every junction the collection's own fields own. Two many-to-many fields cannot share one junction table, since a link does not record which field made it. Rename one many-to-many field per save — with two renamed in one save the builder cannot tell which old table belongs to which new field, and refuses rather than guess.
Related
- Visual Schema Builder — the interface that creates these
- Collections — the code-first equivalent
- Production migrations