A modern, modular, tree-shakable, zero-runtime-dependency fake data generator for TypeScript and JavaScript.
A production-ready alternative to Faker.js — with React integration and a CLI built in.
Website · Try it live · Docs · npm · GitHub
- 100+ generators across 16 modules (
forge.listGenerators()to see every one) - Zero runtime dependencies
- ESM + CommonJS, one package
- Works in Node.js, Bun, Deno, and browsers — no runtime-specific APIs, only web-standard globals (
btoa,crypto-free PRNG) - 100% typed, every generator has a precise return type
- Tree-shakable — import only the modules/functions you use
- Seedable — deterministic output for tests and snapshots
- 9 built-in locales, plus a custom locale API
- Custom generators via
forge.define(...) - Schema-based generation (
@sahinur/forgedata/zod): fake data straight from a Zod schema - React integration (
@sahinur/forgedata/react): a provider + two hooks - CLI (
forgedata): generate fake data from your terminal or scripts - 100% test coverage (statements/branches/functions/lines)
- Install
- Quick start
- Deterministic output (seeding)
- Locales
- Custom generators
- Unique values
- Helpers & randomness utilities
- Generator introspection
- Zod schema generation
- React integration
- CLI
- Modules
- Runtime support
- Development
- Website
- Publishing
- License
npm install @sahinur/forgedataimport{ForgeData}from"@sahinur/forgedata";constforge=newForgeData();forge.person.fullName();// "Ava Thompson"forge.internet.email();// "ava.thompson@example.com"forge.location.country();// "Canada"forge.finance.creditCardNumber();// "4539123456781234"forge.phone.number();// "+1-555-123-4567"forge.ai.prompt();// "Explain quantum computing to a beginner..."Or use the ready-made default instance for quick scripts:
import{forge}from"@sahinur/forgedata";console.log(forge.person.fullName());constforge=newForgeData({seed: 42});forge.person.fullName();// always the same value for seed 42forge.seed(42);// reseed an existing instanceSeeds accept a number or a string (strings are hashed into a seed), so you can seed with something readable like a test name.
constforge=newForgeData({locale: "ja"});forge.person.fullName();// Japanese name poolforge.locale("fr");// switch at runtimeforge.locale();// "fr" — read the current localeBuilt-in locales: en, bn (Bangla), hi (Hindi), ar (Arabic), ja (Japanese), fr (French), de (German), es (Spanish), zh (Chinese).
forge.defineLocale({code: "pirate",name: "Pirate",person: {firstNamesMale: ["Blackbeard","Redbeard"],firstNamesFemale: ["Anne","Mary"],lastNames: ["Silver","Flint"],jobTitles: ["First Mate"],professions: ["Sailor"],},location: {cities: ["Tortuga"],states: ["Caribbean"],countries: ["Never Land"]},company: {suffixes: ["& Co"],catchPhraseAdjectives: ["Salty"],catchPhraseNouns: ["treasure"]},lorem: {words: ["arr","matey","doubloon"]},});forge.locale("pirate");forge.define("pokemon",(f)=>f.pick(["Pikachu","Bulbasaur","Charmander"]));forge.custom.pokemon();// "Pikachu"forge.unique.email();// never repeats for this ForgeData instanceforge.unique.username();forge.unique.uuid();constuniqueCity=forge.unique.wrap(()=>forge.location.city());uniqueCity();forge.unique.clear();// forget everything generated so farforge.uuid();forge.shuffle([1,2,3]);forge.pick(["a","b","c"]);forge.pickMultiple(["a","b","c","d"],2);forge.randomEnum(MyEnum);forge.probability(0.3);forge.weighted([{weight: 9,value: "common"},{weight: 1,value: "rare"},]);forge.randomArray(()=>forge.person.fullName(),5);Every module is walked via prototype introspection rather than a hand-maintained registry, so this never drifts from the real API:
forge.listGenerators();// [{ module: "person", method: "fullName", id: "person.fullName" }, ...] (100+ entries)forge.invoke("person","fullName");// same as forge.person.fullName()This is what the CLI is built on.
npm install @sahinur/forgedata zodimport{z}from"zod";import{fromZodSchema}from"@sahinur/forgedata/zod";constUserSchema=z.object({id: z.string().uuid(),name: z.string(),email: z.string().email(),age: z.number().int().min(18).max(99),role: z.enum(["admin","user","guest"]),tags: z.array(z.string()).min(1).max(3),bio: z.string().max(140).optional(),});constuser=fromZodSchema(forge,UserSchema);// fully typed as z.infer<typeof UserSchema> — every field respects the// schema's constraints: a valid uuid, a real-looking email, age in [18, 99],// one of the three enum values, 1-3 tags, bio present ~80% of the time.Supports the full range of Zod v4 schema types recursively: primitives (with format-aware strings — .email(), .url(), .uuid(), .datetime(), .ipv4(), .jwt(), and more — plus .min()/.max()/.length()), object, array, tuple, record, map, set, enum/native enums, literal, union/discriminated union, optional/nullable/default/catch, and recursive schemas via z.lazy() (depth-capped, so a self-referential schema can't blow the stack).
.transform()/.pipe() are run through the schema's real safeParse() after generation, so the output reflects the actual post-transform shape. Arbitrary .refine() predicates aren't evaluated ahead of time — a generated value can occasionally fail one, in which case the raw (pre-refine) value is returned rather than throwing. zod is an optional peer dependency (^4.0.0); the main @sahinur/forgedata entry point never imports it, exactly like the React integration.
import{ForgeDataProvider,useForgeData,useGenerator}from"@sahinur/forgedata/react";functionApp(){return(<ForgeDataProviderseed={42}locale="en"><UserCard/></ForgeDataProvider>);}functionUserCard(){// Regenerated only when `deps` changes — stable across re-renders otherwise.constuser=useGenerator((forge)=>({name: forge.person.fullName(),email: forge.internet.email()}),[],);return<div>{user.name} — {user.email}</div>;}useForgeData() works even without a ForgeDataProvider (it falls back to a shared default instance) — wrap your tree in a provider whenever you need a specific seed/locale or per-instance isolation. react is an optional peer dependency; the main @sahinur/forgedata entry point never imports it.
npx --package=@sahinur/forgedata forgedata list # every generator id
npx --package=@sahinur/forgedata forgedata list --module person # filtered to one module
npx --package=@sahinur/forgedata forgedata generate person.fullName # one value
npx --package=@sahinur/forgedata forgedata generate internet.email --count 5 --seed 42
npx --package=@sahinur/forgedata forgedata generate location.country --locale ja --jsonInstall it globally (npm install -g @sahinur/forgedata) to just run forgedata ... directly; see the docs site for the full flag reference.
| Module | Examples |
|---|---|
person | firstName(), lastName(), fullName(), gender(), jobTitle(), profession() |
internet | email(), username(), password(), url(), domain(), ipv4(), ipv6(), mac(), userAgent(), jwt(), apiKey(), jwtSecret() |
company | name(), catchPhrase(), logo() |
finance | currency(), amount(), creditCardNumber(), creditCardCVV(), iban(), bitcoinAddress(), ethereumAddress(), cryptoCoin(), stockSymbol() |
location | country(), state(), city(), zipCode(), latitude(), longitude(), timezone(), airport() |
commerce | productName(), price(), department(), isbn(), barcode() |
phone | number(), imei() |
date | past(), future(), recent(), birthdate(), month(), weekday(), between() |
image | avatar(), url(), category(), dataUri() |
lorem | word(), words(), sentence(), paragraph(), slug(), markdown(), html() |
color | hex(), rgb(), rgba(), hsl(), cssColorName(), tailwindColor(), materialColor(), svgPattern() |
vehicle | manufacturer(), model(), type(), vin() |
animal | type(), name() |
science | unit(), chemicalElement() |
ai | prompt(), chatConversation(), codeSnippet(), sqlQuery(), json(), markdown(), apiResponse(), logLine(), commitMessage(), issueTitle(), prDescription() |
misc | emoji(), hashtag(), programmingLanguage(), githubUsername(), gitCommitHash(), semver(), dockerImageName(), npmPackageName(), otp(), licenseKey(), qrData(), movie(), book(), music(), food(), holiday(), university() |
Run forge.listGenerators().length to see the exact live count (100+).
ForgeData avoids Node-only APIs in src/ — it uses only btoa, Math/Date, and standard JS, all of which exist in Node 18+, Bun, Deno, and every modern browser. It's published as ESM + CJS so:
- Node.js:
import/requireboth work out of the box. - Bun: works via the same ESM/CJS builds.
- Deno:
import { ForgeData } from "npm:@sahinur/forgedata"; - Browsers:
import { ForgeData } from "@sahinur/forgedata";through any bundler, orexamples/browser.htmlfor a zero-bundler example.
npm install
npm run build # tsup -> dist (ESM + CJS + .d.ts, for index/react/zod/cli)
npm run test:coverage # vitest, enforces 100% coverage thresholds
npm run lint
npm run bench # micro-benchmark, compares against faker if installed
npm run cli -- list # run the CLI from source during developmentSee the documentation site (built from docs/docs.html) for the getting-started guide, full API reference, React guide, CLI reference, migration guide from Faker.js, and contributing guide. See examples/ for runnable Node, browser, React, and CLI samples.
docs/ is a plain HTML + Tailwind CSS landing page and single-page documentation site (no framework, no server-side build):
cd docs
npm install
npm run build # compiles src/input.css -> dist/output.css (commit the result)
npm run serve # or just open index.html directlyDeployed straight from the repo — no CI needed: Settings → Pages → Source → Deploy from a branch → main / docs. Because there's no build step in that flow, docs/dist/output.css is committed rather than gitignored; run npm run build in docs/ and commit the output whenever you touch docs/src/input.css or add new Tailwind classes to the HTML.
This repo is fully set up to publish, but nothing publishes itself:
npm run preflight # lint + typecheck + 100%-coverage tests + build
npm login
npm publish --access publicscripts/publish.sh wraps that sequence with a confirmation prompt, and RELEASE_NOTES.md has a ready-to-paste description for the GitHub Release / npm announcement.
For every release after the first, .github/workflows/release.yml runs semantic-release on pushes to main — it derives the next version from Conventional Commits, updates CHANGELOG.md, tags, and publishes, but only once you add an NPM_TOKEN repository secret. Until then it's a no-op (see the docs site's Contributing section); it never requires a token to just sit there ready.
MIT