ctx.lib is a platform-curated allowlist, not a general import mechanism — see [The SDK & security model](glyphs/the-sdk) for why it exists and what it deliberately excludes. This page is the complete, per-function reference: every entry that exists today, in both languages, with one real worked example each. The authoritative source this page is generated from is the platform's curated library catalog (src/lambda/app/glyph-runtime/python/glyph-library-catalog.json in the platform repo) — if you ever find a mismatch, that file is the one to trust.
Tier 1 — stdlib & builtin (zero third-party dependency)
Everything in this tier ships inside the language runtime itself — nothing extra was installed to add these. Each one is scoped down to a hand-picked set of members (never the whole module) for anything with even a small surface for risk.
Python — data shape & text
| Call | Example |
|---|---|
| ctx.lib.json.dumps / .loads / .JSONDecodeError | ctx.lib.json.dumps({'total': 42}) → '{"total": 42}' |
| ctx.lib.re — search/match/fullmatch/findall/finditer/sub/split/compile/escape + flags | ctx.lib.re.sub(r'\\s+', ' ', ' a b ') → 'a b' |
| ctx.lib.textwrap — wrap/fill/shorten/dedent/indent | ctx.lib.textwrap.shorten('A very long product description', width=20, placeholder='...') → 'A very long...' |
| ctx.lib.difflib — SequenceMatcher/unified_diff/get_close_matches | ctx.lib.difflib.get_close_matches('aple', ['apple', 'orange']) → ['apple'] — handy for "did you mean" on a free-text field. |
| ctx.lib.string — ascii_letters/digits/punctuation/Template | ctx.lib.string.Template('Hi $name').substitute(name='Sam') → 'Hi Sam' — fills placeholders only, cannot evaluate expressions. |
Python — numbers & collections
| Call | Example |
|---|---|
| ctx.lib.math.* (whole module) | ctx.lib.math.ceil(4.1) → 5 |
| ctx.lib.statistics.* (whole module) | ctx.lib.statistics.median([3, 1, 2]) → 2 |
| ctx.lib.decimal.Decimal / getcontext / setcontext / localcontext | ctx.lib.decimal.Decimal('19.99') * 3 → Decimal('59.97') — exact money math, no float rounding error. |
| ctx.lib.itertools.* (whole module) | list(ctx.lib.itertools.chain([1,2],[3,4])) → [1, 2, 3, 4] |
| ctx.lib.collections — Counter/defaultdict/namedtuple/OrderedDict/deque | ctx.lib.collections.Counter(['a','b','a']).most_common(1) → [('a', 2)] |
| ctx.lib.bisect.* (whole module) | ctx.lib.bisect.bisect_left([10, 20, 30], 15) → 1 — where 15 would insert to keep the list sorted. |
| ctx.lib.heapq.* (whole module) | ctx.lib.heapq.nlargest(2, [5, 1, 9, 3]) → [9, 5] |
| ctx.lib.copy.copy / deepcopy | clone = ctx.lib.copy.deepcopy(order); clone['items'][0]['qty'] = 2 — mutate the clone, source stays untouched. |
Python — functional helpers, dates, and tokens
| Call | Example |
|---|---|
| ctx.lib.functools — reduce/partial/lru_cache/cmp_to_key | ctx.lib.functools.reduce(lambda a, b: a + b, [1, 2, 3, 4]) → 10 |
| ctx.lib.operator — itemgetter/attrgetter/methodcaller/add/sub/mul/truediv/and_/or_/not_ | sorted(rows, key=ctx.lib.operator.itemgetter('price')) — sort a list of dicts by one field, no lambda needed. |
| ctx.lib.datetime — datetime/date/time/timedelta/timezone | ctx.lib.datetime.date.today() + ctx.lib.datetime.timedelta(days=7) — a week from today. |
| ctx.lib.uuid.uuid4() | str(ctx.lib.uuid.uuid4()) → a random UUID string, e.g. '3f1b...' |
| ctx.lib.secrets — token_hex/token_urlsafe/token_bytes/compare_digest | ctx.lib.secrets.token_urlsafe(16) → a URL-safe random token — same entropy source as uuid4, meant for API keys/invite codes rather than record IDs. |
| ctx.lib.hashlib — sha256/sha1/sha512/sha384/md5/blake2b/blake2s/new/pbkdf2_hmac | ctx.lib.hashlib.sha256('hello'.encode('utf-8')).hexdigest() → a hex digest. md5/sha1 are there for checksums and legacy signatures, not for new security-sensitive use. |
| ctx.lib.hmac — new/digest/compare_digest | ctx.lib.hmac.new(key, msg, ctx.lib.hashlib.sha256).hexdigest() → a signature you can verify later; compare two signatures with compare_digest so timing reveals nothing. |
| ctx.lib.random.Random(seed) | r = ctx.lib.random.Random(user_id); r.shuffle(items) → the same shuffle for the same seed every time — a stable per-user order. Not cryptographically secure: use secrets for tokens. Only the Random class is exposed, so there is no shared global generator to mutate. |
Node — data shape & text
| Call | Example |
|---|---|
| ctx.lib.util.format(...) / .inspect(...) | ctx.lib.util.format('%s owes $%d', 'Sam', 42) → 'Sam owes $42' |
| ctx.lib.querystring.parse(...) / .stringify(...) | ctx.lib.querystring.stringify({ ref: 'email', id: 7 }) → 'ref=email&id=7' |
| ctx.lib.path — whole module (join/basename/dirname/extname/normalize/parse/...) | ctx.lib.path.join('invoices', '2026', 'inv-1.pdf') → 'invoices/2026/inv-1.pdf' — pure string manipulation, never touches the filesystem. |
| ctx.lib.url.URL | new ctx.lib.url.URL('https://shop.example.com/p?id=7').searchParams.get('id') → '7' |
Node — crypto, buffers, and IDs
| Call | Example |
|---|---|
| ctx.lib.crypto — randomUUID/createHash/createHmac/randomBytes/randomInt/timingSafeEqual | ctx.lib.crypto.createHmac('sha256', key).update(message).digest('hex') → a signature; ctx.lib.crypto.createHash('sha256').update('hello').digest('hex') → a hex digest; timingSafeEqual compares two same-length buffers without leaking where they differ. |
| ctx.lib.random.Random(seed) | const r = ctx.lib.random.Random(userId); r.shuffle(items) → the same shuffle for the same seed every time. Methods: random(), randint(a, b), shuffle(array), choice(array), sample(array, k). Not cryptographically secure — use ctx.lib.crypto.randomBytes/randomInt for tokens. |
| ctx.lib.uuid.v4() | ctx.lib.uuid.v4() → a random UUID string. |
| ctx.lib.buffer — from/alloc/concat/compare/isBuffer | ctx.lib.buffer.from('hello').toString('base64') → 'aGVsbG8=' |
| ctx.lib.structuredClone.clone(value) | const copy = ctx.lib.structuredClone.clone(order); copy.items[0].qty = 2; — mutate the clone, source stays untouched. |
Why no Buffer.allocUnsafe
ctx.lib.buffer deliberately leaves out allocUnsafe/allocUnsafeSlow — they return uninitialized memory, which on a reused (warm) sandbox container could theoretically contain bytes left over from a previous invocation. Every exposed Buffer method here allocates zeroed or explicitly-filled memory instead.
Node — validation
| Call | Example |
|---|---|
| ctx.lib.assert — ok/strictEqual/deepStrictEqual/notStrictEqual/match/throws/AssertionError | ctx.lib.assert.ok(request.qty > 0, 'qty must be positive') — throws (your Glyph's error result) if the condition is false. |
Tier 2 — vetted third-party packages
Real, pinned npm/pip dependencies — the first ones this feature ever added. Each was vetted down to its full dependency tree (not just the headline package) before being allowed in: python-dateutil's only dependency is six (zero I/O); lodash has zero dependencies at all. Both are cherry-picked to specific functions, not the whole package surface.
Python — python-dateutil
| Call | Example |
|---|---|
| ctx.lib.dateutil_parser.parse(dateString) | ctx.lib.dateutil_parser.parse('March 15, 2026') → a real datetime — parses far more formats than datetime.strptime without you specifying the exact pattern. |
| ctx.lib.dateutil_relativedelta.relativedelta(...) | ctx.lib.datetime.date(2026, 1, 31) + ctx.lib.dateutil_relativedelta.relativedelta(months=1) → 2026-02-28 — calendar-aware month math (clamps to the real last day of February), something a plain timedelta can't express at all. |
Why two separate ctx.lib entries for one package
dateutil_parser and dateutil_relativedelta are two different submodules of python-dateutil, kept as two ctx.lib names rather than merged into one, so each can be vetted and documented against its own real, common use case — free-text date parsing vs. calendar-aware date math.
Node — lodash (cherry-picked)
| Call | Example |
|---|---|
| ctx.lib.lodash.get(obj, path, fallback?) | ctx.lib.lodash.get(order, 'customer.address.city', 'Unknown') — safe path access into a shape you don't fully control, with a default. |
| ctx.lib.lodash.set(obj, path, value) | ctx.lib.lodash.set(order, 'customer.address.city', 'Austin') — the inverse of get. |
| ctx.lib.lodash.merge(dest, ...sources) | ctx.lib.lodash.merge({ a: 1 }, { b: 2 }) → { a: 1, b: 2 } — a real deep merge, unlike Object.assign's shallow one. |
| ctx.lib.lodash.isEqual(a, b) | ctx.lib.lodash.isEqual({ a: [1,2] }, { a: [1,2] }) → true — deep equality === can't do. |
| ctx.lib.lodash.cloneDeep(value) | const copy = ctx.lib.lodash.cloneDeep(order); |
| ctx.lib.lodash.pick(obj, keys) / omit(obj, keys) | ctx.lib.lodash.pick(customer, ['id', 'email']) → just those two fields. |
| ctx.lib.lodash.sortBy(arr, iteratee) | ctx.lib.lodash.sortBy(items, 'price') — sort an array of objects by one field, no comparator function to write. |
| ctx.lib.lodash.uniqBy(arr, iteratee) | ctx.lib.lodash.uniqBy(items, 'sku') — de-dupe by one field. |
| ctx.lib.lodash.chunk(arr, size) | ctx.lib.lodash.chunk([1,2,3,4,5], 2) → [[1,2],[3,4],[5]] — e.g. splitting a list into pages of N. |
What's deliberately NOT in ctx.lib.lodash
debounce/throttle/delay are left out — they're time-based, and a Glyph invocation is stateless and one-shot (no persistent event loop across calls for them to debounce against). template is left out because it can evaluate arbitrary interpolated code — exactly the class of risk ctx.lib exists to close off.
A lot is already free, before you even reach for ctx.lib
Node's sandbox runs on a real V8 engine, so the standard JavaScript language itself — not a Node addition — is already fully available with zero ctx.lib call: Array.prototype.{map,filter,reduce,flat,sort,find,...}, Object.{keys,values,entries,assign,fromEntries,groupBy}, Map, Set, Promise, Math, JSON, RegExp, Date, and Intl (number/date/list formatting) all work natively. What's blocked is specifically Node's own module system and process-level globals (require, process, Buffer, global) — that's the gap ctx.lib's Node entries close, deliberately narrowly. Python's standard library, by contrast, genuinely requires an import for nearly everything beyond bare language syntax, which is why ctx.lib's Python side covers more ground.