JSON to Mongoose Schema
Generate Mongoose schemas from JSON instantly. Supports JavaScript and TypeScript output, timestamps option, nested objects, and arrays. Free, runs in your browser.
- Runs in your browser
- Your data never leaves your browser
- Free · No Sign-Up
Scan with WeChat to share this tool
Examples, details and FAQ Worked examples, how it compares with other tools, and answers to common questions.
Type Mapping
nullfits any type and is skipped; a field that isnullin every sample →mongoose.Schema.Types.Mixed- a field whose samples have different types (number and string, object and string) →
mongoose.Schema.Types.Mixed; ISO date-times mixed with other strings →String - string that starts like an ISO 8601 date-time (
2026-10-01T09:30:00Z) →Date; a plain date such as2026-10-01staysString - string →
String - number →
Number - boolean →
Boolean - array of primitives →
[String]/[Number]/[Boolean] - array of objects →
[subSchema](separate schema generated) - nested object → separate schema variable referenced inline
Example
Given this JSON with model name User:
{"username": "alice", "age": 28, "tags": ["admin"], "address": {"city": "London"}}
JavaScript output (timestamps On):
const mongoose = require('mongoose');
const { Schema } = mongoose;
const addressSchema = new Schema({
city: { type: String },
});
const userSchema = new Schema({
username: { type: String },
age: { type: Number },
tags: [String],
address: addressSchema,
}, { timestamps: true });
module.exports = mongoose.model('User', userSchema);
Sub-schemas are named after the key (address → addressSchema) and written before the schema that uses them,
so the file runs top to bottom. The model name is converted to PascalCase for mongoose.model(); Mongoose then derives the
collection name by lowercasing and pluralizing it (User → users).
Example: TypeScript With required On
Model name product, TypeScript, timestamps On, required On:
{"sku":"A-1","price":9.5,"created_at":"2026-10-01T09:30:00Z","release-date":"2026-10-01",
"variants":[{"color":"red","stock":3},{"color":"blue","size":"M"}],"meta":null}
import mongoose, { Schema } from 'mongoose';
export interface IVariants {
color: string;
stock: number;
size: string;
}
export interface IProduct {
sku: string;
price: number;
created_at: Date;
"release-date": string;
variants: IVariants[];
meta: any;
createdAt: Date;
updatedAt: Date;
}
const variantsSchema = new Schema({
color: { type: String, required: true },
stock: { type: Number, required: true },
size: { type: String, required: true },
});
const productSchema = new Schema<IProduct>({
sku: { type: String, required: true },
price: { type: Number, required: true },
created_at: { type: Date, required: true },
"release-date": { type: String, required: true },
variants: [variantsSchema],
meta: { type: mongoose.Schema.Types.Mixed, required: true },
}, { timestamps: true });
export default mongoose.model<IProduct>('Product', productSchema);
Two things to fix by hand before using it:
sizeandstockeach appear in only one variant, yet both arerequired. The array elements are merged into one sub-schema with every key, and the switch applies to all of them.metawasnull, so it isMixedandrequired: Mongoose would reject the very document you pasted. Give it a real type or droprequired.
The interface is a plain document interface, passed as Schema<IProduct> and mongoose.model<IProduct>, as in the Mongoose TypeScript guide (“Using Generics”); it does not extend Document. With timestamps On, Mongoose adds createdAt and updatedAt of type Date (Timestamps), so the interface lists both and doc.createdAt type-checks on a document from new Product() and on a .lean() result. created_at from the JSON is a separate field. If the JSON already has a createdAt or updatedAt key, its own type is kept, because Mongoose then keeps that path as you defined it. The tool’s test compiles every generated TypeScript file with TypeScript 5.9.3 in strict mode against Mongoose 9.10.3.
Keys that are not JavaScript identifiers, such as release-date, are quoted so the generated file parses.
Example: Every Sample Decides the Type
A root array with two orders, model name Order, JavaScript, timestamps Off:
[{"code":1,"note":null,"seller":{"meta":{"x":1}},"buyer":{"meta":{"y":"2"}}},{"code":"A-2","note":"gift","tags":["a",1]}]
Output:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const metaSchema = new Schema({
x: { type: Number },
});
const sellerSchema = new Schema({
meta: metaSchema,
});
const buyerMetaSchema = new Schema({
y: { type: String },
});
const buyerSchema = new Schema({
meta: buyerMetaSchema,
});
const orderSchema = new Schema({
code: { type: mongoose.Schema.Types.Mixed },
note: { type: String },
seller: sellerSchema,
buyer: buyerSchema,
tags: [mongoose.Schema.Types.Mixed],
}, { timestamps: false });
module.exports = mongoose.model('Order', orderSchema);
codeis1in one order and“A-2”in the other, so it isMixed;Numberwould reject“A-2”.noteisnullonce and“gift”once, so it isString.tagsmixes a string and a number, so its elements areMixed.- The two
metaobjects have different fields, so the second gets the parent key as a prefix (buyerMetaSchema). Objects under the same key with the same fields and types share one schema; if the prefixed name is taken too, a number is added.
The tool’s test runs each generated JavaScript file with Mongoose 9.10.3, builds a document from every sample, and checks that validateSync() passes and that every field and value comes back (ISO date-times come back as Date, in toISOString() form; an array path missing from a sample comes back as [], Mongoose’s default for arrays).
Limits
- Mixed accepts anything. A field typed
Mixedbecause its samples disagree is not cast or validated, and Mongoose does not track changes inside it unless you callmarkModified(). Pick one type when the data allows it. - Only objects at the root. In a root array, numbers, strings and arrays next to the objects are ignored; the schema describes the objects.
- No ObjectId, enum, index or validation. A 24-character hex string stays
String; addSchema.Types.ObjectIdandref,unique,enumormin/maxyourself. - Only the root schema is typed. Sub-schemas get an interface for the root interface to use, but
new Schema()for them has no type argument, and fields are not optional even whenrequiredis Off. Mongoose does not check that the interface matches the schema (TypeScript guide), so mark optional fields with?yourself. - A root array of primitives such as
[“a”, “b”]generates no schema, only the comment// Array of primitives — no schema to generate.
For plain TypeScript types or a Zod schema from the same JSON, use JSON to TypeScript or JSON to Zod.
FAQ
What does this tool generate?
It generates Mongoose schema definitions from JSON. Each nested object becomes its own named schema, and a model export is included at the bottom.
What language modes are supported?
JavaScript (CommonJS require/module.exports) and TypeScript (ES module import/export with a plain document interface passed to Schema<T> and model<T>, as in the Mongoose TypeScript guide).
How are JSON types mapped to Mongoose types?
string → String, number → Number, boolean → Boolean, null → mongoose.Schema.Types.Mixed when every sample is null, array of primitives → [Type], array of objects → separate sub-schema, nested object → separate sub-schema.
What does the timestamps option do?
When On, the schema is created with { timestamps: true }, which automatically adds createdAt and updatedAt fields managed by Mongoose. In TypeScript mode the interface also lists createdAt: Date and updatedAt: Date, unless your JSON already has a key with that name.
Are all fields marked as required?
Not by default, since a JSON sample cannot show which fields are required. Turning the required switch On adds required: true to every field except nested schemas and arrays of objects; then remove it where a field is optional. Note that Mongoose's required validator rejects null, so a field that is null in your sample cannot stay required.
Is my data sent to a server?
No. The entire conversion runs in your browser. No data leaves your machine.