npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@judge452/personafaker-postman

v1.1.0

Published

PersonaFaker client for Postman pre-request scripts and Newman. Replaces {{$persona.*}} placeholders in your request with deterministic synthetic PII.

Readme

@judge452/personafaker-postman

A tiny client that lets you embed deterministic synthetic personas (names, addresses, cards, IBANs, …) into Postman requests by just sprinkling {{$persona.*}} placeholders into your URL, headers, or body.

  • Same person every time for the same (client, user_id) — until you rotate them. Browse a customer's life cycle by replaying the same collection.
  • Works in Postman GUI and Newman. No native Postman runtime hooks, just a pre-request script.
  • Vanilla JS, zero dependencies. Paste the source into Postman or npm install it for Newman / CI.

Need an API key? Sign up at https://personafaker.com, create one in the cabinet, and pop it into a Postman environment variable.


Install

Newman / CI

npm install --save-dev @judge452/personafaker-postman
// pre-request script
const PersonaFaker = require('@judge452/personafaker-postman');

Postman desktop / web

Three loading paths, in order of preference. They produce the same Persona / PersonaFaker symbol — only the install step differs.

1. pm.require('npm:…') — Postman v11+, zero setup

Postman v11 ships an in-script package loader that pulls scoped npm packages on demand. Nothing to install in the UI:

  1. Open your collection → Pre-request Script tab (collection-level, not request-level — the library should run once per script run).

  2. Paste:

    const Persona = pm.require('npm:@judge452/[email protected]');
    
    Persona.init({
      pm,                                              // required when loaded via pm.require
      apiKey: pm.environment.get('PERSONA_API_KEY'),
      userIdPerRequest: true,
      localeRandom: ['DE', 'FR', 'ES', 'IT', 'NL'],
    });
    
    Persona.resolve((err) => { if (err) console.error('[Persona]', err.message); });
  3. Hit Save on the collection. Fire any request below that uses {{$persona.*}} placeholders.

The first run downloads the package; subsequent runs use Postman's cache. Pin the version (@1.0.2) so collections stay reproducible — omitting it grabs latest and your QA flows can drift when we ship.

2. Package Library — workspace-wide reuse

If multiple collections in the same workspace use the library, register it once via Postman's Package Library so each collection just require()s it without restating the npm coordinate.

  1. WorkspacePackages (left sidebar) → Create package.

  2. Name it personafaker-postman. Paste the entire contents of node_modules/@judge452/personafaker-postman/dist/index.js (or grab dist/index.js from the GitHub release) into the editor.

  3. Save. Postman assigns the package an internal id.

  4. In any collection's pre-request script:

    const Persona = pm.require('personafaker-postman');
    Persona.init({ pm, apiKey: pm.environment.get('PERSONA_API_KEY'), userIdPerRequest: true });
    Persona.resolve();

Updating the library = paste a new version into the same package; every collection in the workspace picks it up on the next run.

3. Paste-in — works on every Postman version, no pm.require needed

If you're on Postman ≤ v10 (no Package Library, no pm.require) or prefer to vendor the source into the collection itself:

  1. Grab dist/index.js from the published tarball (npm pack @judge452/personafaker-postman and crack open the tgz), or copy the file straight from this repo at clients/postman/dist/index.js.

  2. Open your collection → Pre-request Script tab.

  3. Paste the entire contents of dist/index.js at the top.

  4. Below it, in the same script, configure and resolve:

    // (paste of dist/index.js above this line)
    
    PersonaFaker.init({
      apiKey: pm.environment.get('PERSONA_API_KEY'),
      userIdPerRequest: true,
    });
    PersonaFaker.resolve();

In paste-in mode the library installs itself on globalThis.PersonaFaker, so you don't need to capture a return value or pass pm — the script already has both in scope.

Tip — sample collection. A ready-to-import collection that demonstrates all three persona modes (static / per-request / from-header) lives at examples/sample-collection.json in this repo. Postman → Import → drop the file → set the PERSONA_API_KEY environment variable → run.


Quickstart

The library exposes two equivalent flows for kicking off the fill: resolve(cb) (callback) and resolveAsync() (Promise). Pick one based on what else your pre-request script does. The rules:

| If your script… | Use | Why | | --- | --- | --- | | Just fills placeholders, nothing else | eitherresolve() is shortest | Fill is the only thing that has to happen before the request is sent. | | Has follow-up code that reads / mutates pm.request (signing, HMAC, content-hash, JWT body, recomputed Content-Length, custom headers derived from the body, …) | resolveAsync() + await OR resolve(cb) with the follow-up inside the callback | The fill mutates pm.request.body / headers / url asynchronously. Anything that runs synchronously after resolve() returns sees the un-filled {{$persona.*}} strings — and gets clobbered when the fill lands moments later. |

⚠ The trap: PersonaFaker.resolve() returns the moment it queues the API call, not when the fill lands. Code on the next line runs before the fill. If that code touches the request body, it sees raw placeholders, and any state it derives (signatures, hashes) becomes stale the instant the fill arrives.

Collection-level pre-request script — both flavours:

// const PersonaFaker = require('@judge452/personafaker-postman');  // Newman
// (in Postman GUI: paste the source, then `PersonaFaker` is already defined)

PersonaFaker.init({
  pm,                             // required when loaded via require / pm.require
  apiKey: pm.environment.get('PERSONA_API_KEY'),

  // who is this persona?  (pick exactly one)
  userIdPerRequest: true,         // brand new persona on every request

  // where do they live?  (pick at most one; omit to let the API choose)
  localeRandom: ['DE', 'FR', 'ES', 'IT', 'NL'],

  // optional: per-requisite defaults that apply whenever a placeholder
  // for that requisite shows up in a request below
  requisites: {
    phone: { type: 'mobile' },
    card:  { brand: 'visa' }
  }
});

Flow A — resolve(cb) — callback

Use this when you want to keep things flat and don't need await:

PersonaFaker.resolve((err) => {
  if (err) return console.error('[PersonaFaker]', err);
  // ✅ pm.request is fully filled at this point.
  // Put any signing / hashing / header-mutation code here.
});

Postman's sandbox keeps the request on hold until the callback returns, so anything inside the callback runs before the actual HTTP request fires. Anything after PersonaFaker.resolve(...) (outside the callback) is racey — don't put body-dependent logic there.

Flow B — resolveAsync() — async/await

Use this when you have multiple async steps to chain, or just prefer a linear top-to-bottom script:

await PersonaFaker.resolveAsync();
// ✅ pm.request is fully filled at this point — same guarantee as the callback.
//    Sign / hash / mutate freely on the lines that follow.

The pre-request script must already be in an async context for await to work. Postman's modern (v10+) sandbox treats every script as async by default, so you can write await at the top level. Newman ≥ v6 is the same. If you're stuck on an old Newman, wrap the script body in (async () => { … })() and call done() inside (or fall back to Flow A).

What not to do

PersonaFaker.resolve();        // returns instantly — fill not done yet
signRequest(pm.request);       // ⚠ signs the literal "{{$persona.*}}";
                               //   fill arrives later, body changes,
                               //   signature on the wire is now wrong.

Same trap with the async form if you forget await:

PersonaFaker.resolveAsync();   // Promise discarded — same race as above
signRequest(pm.request);       // ⚠

Request body for either flow:

POST {{baseUrl}}/signup
Content-Type: application/json

{
  "name":   "{{$persona.name}}",
  "email":  "{{$persona.email}}",
  "phone":  "{{$persona.phone}}",
  "address": {
    "line":     "{{$persona.address.formatted}}",
    "city":     "{{$persona.address.city}}",
    "postcode": "{{$persona.address.postal_code}}",
    "country":  "{{$persona.address.country}}"
  },
  "card": {
    "number": "{{$persona.card.pan}}",
    "expiry": "{{$persona.card.expiry_string}}",
    "cvv":    "{{$persona.card.cvv}}"
  }
}

When the request fires, every placeholder is replaced in-place with the generated values. The library makes one call to the PersonaFaker API per script run, no matter how many placeholders are present.


Configuration reference

PersonaFaker.init(options) accepts:

| Option | Type | Default | Description | | ------------------- | -------------------- | ------------------------ | --- | | pm | object | globalThis.pm | The Postman script's pm object. Required when the library is loaded via require() / pm.require('npm:…') because the package runs in its own scope and can't see the script's pm global. Optional in paste-in mode. | | apiKey | string (required) | — | Your PersonaFaker API key (psk_… or oat_…). | | baseUrl | string | https://api.personafaker.com | Override for self-hosted / staging. | | userId | string | — | Static persona id. Use it when one collection ↔ one persona. | | userIdVar | string | — | Read the id from a Postman variable (env / collection / global). | | userIdPath | string | — | Extract the id from the request itself. See Path expressions. | | userIdPerRequest | boolean | false | Mint a new random id on every script run. | | locale | string | — | Static ISO-2 locale, e.g. 'DE'. | | localeVar | string | — | Read locale from a Postman variable. | | localePath | string | — | Extract locale from the request body / headers / URL. | | localeRandom | string[] | — | Pool of locales to pick uniformly at random per script run. | | requisites | Record<string, object> | {} | Per-requisite default options forwarded to the API (see below). | | transliterate | 'none' \| 'ascii' | 'none' | If 'ascii', names/addresses are transliterated. | | log | boolean | false | When true, the library writes the full flow trace to the Postman console: init summary, request scan, placeholder count, resolved user_id / locale, API timing + cache split, per-requisite source/value previews, per-fill, replacement summary. Errors always go to console.error regardless of this setting. | | timeoutMs | number | 10000 | PersonaFaker API request timeout. |

Pick at most one of userId / userIdVar / userIdPath / userIdPerRequest. Same with locale (and locale is optional — the API will pick one if you don't).

Path expressions

userIdPath and localePath use a tiny dotted-path mini-language to pull a value out of the request the user is firing:

| Form | Reads from | | ------------------------ | ------------------------------------------------ | | body.email | top-level field of a JSON raw body | | body.user.email | nested field of a JSON raw body | | body.email | named field of a urlencoded / formdata body | | body.email | top-level GraphQL variables field | | header.X-Customer-Id | header value (case-insensitive) | | query.email | URL query parameter | | path.2 | URL path segment (0-based) |

If the path can't be resolved, resolve() reports an error.


Placeholder syntax

{{$persona.<requisite>}}                    → default field for that requisite
{{$persona.<requisite>.<field>}}            → named sub-field

Whitespace inside the braces is ignored. Field names mirror the wire format from the PersonaFaker API exactly (snake_case).

If a placeholder references an unknown requisite, resolve() returns an error. If it references an unknown field on a known requisite, it quietly resolves to an empty string — so typos like {{$persona.address.zipcode}} won't blow up, they just leave the field empty.


Requisite reference

The library can fill any requisite the PersonaFaker API supports. Default field is what you get when you write the bare {{$persona.<name>}} form.

name

| Field | Example | | --------------- | ------------------------------- | | full_name ✱ | "Anna Müller" | | first_name | "Anna" | | middle_name | null | | last_name | "Müller" | | gender | "female" |

requisites.name = { gender: 'male' | 'female' } to constrain.

email

| Field | Example | | -------------- | ------------------------ | | address ✱ | "[email protected]" |

requisites.email = { provider: 'gmail' | 'outlook' | … } — see the API docs for the full list.

phone

| Field | Example | | ----------------- | -------------------- | | e164 ✱ | "+4915123456789" | | national | "0151 23456789" | | international | "+49 151 23456789" | | country_code | "49" | | country | "DE" | | type | "mobile" |

requisites.phone = { type: 'mobile' | 'landline' | 'voip' }.

address

| Field | Example | | --------------- | ----------------------------- | | formatted ✱ | "Hauptstraße 12, 10115 Berlin, Germany" | | country | "DE" | | country_name | "Germany" | | region | "Berlin" | | region_code | "BE" | | city | "Berlin" | | street | "Hauptstraße" | | house_number | "12" | | floor | "3" (or null) | | apartment | "B" (or null) | | postal_code | "10115" |

dob, gender, nationality

| Requisite | Default field | Other fields | | ------------ | --------------- | ---------------------- | | dob | value ✱ ISO date "1991-04-23" | — | | gender | value"male" / "female" / "nonbinary" | — | | nationality| country_name"Germany" | country ("DE") |

requisites.nationality = { country: 'PL' } to force a specific nationality independent of the persona's locale.

iban

| Field | Example | | ------------ | -------------------------------- | | iban ✱ | "DE89370400440532013000" | | formatted | "DE89 3704 0044 0532 0130 00" | | bic | "COBADEFFXXX" | | bank_name | "Commerzbank" | | country | "DE" |

card

| Field | Example | | ---------------- | -------------------- | | pan_formatted ✱| "4111 1111 1111 1111" | | pan | "4111111111111111" | | bin | "411111" | | last4 | "1111" | | brand | "visa" | | issuer | "Chase" | | country | "US" | | type | "credit" | | cardholder | "ANNA MULLER" | | expiry_month | 9 | | expiry_year | 2029 | | expiry_string | "09/29" | | cvv | "123" |

requisites.card = { brand: 'visa' | 'mastercard' | 'amex' | … }.

passport, id_card, ssn

| Requisite | Default field | Other fields | | ---------- | ------------- | ---------------------------------------------------- | | passport | number ✱ | issuing_country, issued_date, expiry_date | | id_card | number ✱ | type, country, issued_date, expiry_date | | ssn | number ✱ | type, country |

ip

| Field | Sample | | ---------- | ----------------------- | | address ✱| "91.198.174.215" | | version | "ipv4" | "ipv6" | | cidr | "91.198.174.0/24" | | country | "DE" |

requisites.ip = { version: 'ipv4' | 'ipv6' | 'any' }. The locale must have CIDR data loaded; otherwise the response carries is_documentation_range: true and an RFC 5737 / 3849 stub address.

✱ = the field used when you write the bare {{$persona.<name>}} form.


Recipes

Always the same persona for this collection

PersonaFaker.init({
  apiKey: pm.environment.get('PERSONA_API_KEY'),
  userId: 'qa-flow-001',
  locale: 'DE'
});
PersonaFaker.resolve();

One persona per logged-in user

Bind the persona to whatever id is already on the request:

PersonaFaker.init({
  apiKey: pm.environment.get('PERSONA_API_KEY'),
  userIdPath: 'header.X-Customer-Id'
});
PersonaFaker.resolve();

A fresh stranger every request, in a random EU country

PersonaFaker.init({
  apiKey: pm.environment.get('PERSONA_API_KEY'),
  userIdPerRequest: true,
  localeRandom: ['DE','FR','ES','IT','NL','PL','PT','SE','BE','AT']
});
PersonaFaker.resolve();

Sign / hash / mutate the request after fill

Two equivalent patterns — pick the one that matches the rest of your script. See Quickstart → Flow A / Flow B for the full explanation of why the position of your follow-up code matters.

// Flow A — callback. Signing goes inside the cb so it runs after fill.
PersonaFaker.init({ pm, apiKey: pm.environment.get('PERSONA_API_KEY'), userIdPerRequest: true });
PersonaFaker.resolve((err) => {
  if (err) return console.error('[PersonaFaker]', err);
  signRequest(pm.request);
});
// Flow B — async/await. Signing is just the next line after `await`.
PersonaFaker.init({ pm, apiKey: pm.environment.get('PERSONA_API_KEY'), userIdPerRequest: true });
await PersonaFaker.resolveAsync();
signRequest(pm.request);

Same rule for any custom logic that reads pm.request.body / pm.request.headers / pm.request.url: do it inside the callback or after await, never on the line below a callback-less resolve().

Debug what the library is doing

PersonaFaker.init({
  pm,
  apiKey: pm.environment.get('PERSONA_API_KEY'),
  userIdPerRequest: true,
  log: true,
});

Open Postman's console (View → Show Postman Console / Cmd+Alt+C) and you'll see:

[PersonaFaker] init: log=on, baseUrl=https://api.personafaker.com, userId=per-request, locale=api-default, transliterate=none
[PersonaFaker] scan: url.path=2, url.query=0, headers=1, body.mode=raw
[PersonaFaker] resolve: 4 placeholders → requisites=[name,email,address]
[PersonaFaker] resolve: user_id=req-…-…, locale=<api-default>
[PersonaFaker] api 142ms: version=1, fresh=yes, generated=3, cached=0
[PersonaFaker]   name (source=generated) full_name=Anna Müller
[PersonaFaker]   email (source=generated) [email protected]
[PersonaFaker]   address (source=generated) formatted=Hauptstr. 12, 10115 Berlin
[PersonaFaker]   fill {{$persona.name}} → Anna Müller
[PersonaFaker]   fill {{$persona.email}} → [email protected]
[PersonaFaker]   fill {{$persona.address.city}} → Berlin
[PersonaFaker]   fill {{$persona.address.formatted}} → Hauptstr. 12, 10115 Berlin
[PersonaFaker] apply: replaced 4 placeholders (headers=1, body=3)

Caching & billing

The PersonaFaker API returns _source: cached on requisites already minted for this (client, user_id), and _source: generated for fresh ones. Re-running the same Postman request with the same userId is free (it returns cached values). userIdPerRequest: true mints fresh data every run.

The library never re-calls the API within a single script run. If you have ten {{$persona.*}} placeholders in one request, that's one HTTP call to PersonaFaker.


Troubleshooting

  • My signature / HMAC / hash doesn't match — request body looks wrong on the wire — classic async-ordering trap. resolve() returns before the fill applies; the call to your signing helper on the next line runs against the literal {{$persona.*}} strings, then the fill lands and rewrites the body, and the signature no longer matches what's actually sent. Move signing inside the resolve(cb) callback, or await resolveAsync() first. See Sign / hash / mutate the request after fill.
  • call init() before resolve() — make sure your collection-level pre-request script runs before any request-level script.
  • resolve() needs the Postman pm object — when the library is loaded via require() or pm.require('npm:…'), the script's pm global isn't reachable from the package's module scope. Pass it in either form — PersonaFaker.init({ pm, apiKey, … }) or PersonaFaker.resolve(pm, cb). The error message prints what we actually received (pm=undefined, pm.request=undefined, etc.) so you can spot a typo or missing field.
  • unknown requisite "..." — check the spelling against Requisite reference.
  • Placeholder stays as literal {{$persona…}} in the sent request — you forgot to call PersonaFaker.resolve(...) (or didn't await the async variant).

Building from source

Published tarballs ship a baked dist/index.js whose DEFAULT_BASE_URL is taken from PERSONA_API_URL at build time (fallback https://api.personafaker.com). Self-hosted forks can rebuild against their own URL:

PERSONA_API_URL=https://api.example.com npm run build

Callers can still override per-instance via PersonaFaker.init({ baseUrl }).


License

MIT