A lens is a face lens: it draws over a tracked face. Screen effects have their own manifest, effect-manifest-v1.md, with its own enums; nothing here applies to them. (Before the split, this manifest also had a kind 2 for screen effects, with durationMs and screen layer types 6–8; they left it inside format-v1.0.0, before anything shipped.)
effect-manifest-v1.md
kind
durationMs
format-v1.0.0
type
anchor
mode
sheet.endBehavior
visible
show
play sprite
source
count
animation
property
easing
endBehavior
Lifecycle names: lens.started. Face-signal names: mouth.open, brows.raised, blink, head.tilt, smile (with optional threshold 0–1; threshold is allowed only on face-signal events). Host names: any ^[a-z][a-z0-9_.-]{0,63}$ — the platform defines the mechanism, the customer the vocabulary.
lens.started
mouth.open
brows.raised
blink
head.tilt
smile
threshold
^[a-z][a-z0-9_.-]{0,63}$
A colour-look asset is a 512 × 512 PNG holding a 64³ LUT as an 8 × 8 grid of 64 × 64 tiles (red across each tile, green down each tile, blue across tiles left→right, top→bottom). Offsets and particle speeds are in face widths.
Full face (anchor 10). The centre of the whole face, brows to chin: face-frame position (0, +0.15) from the eye midpoint, in face widths (rendering-v1 2.2). With scale 1 a sticker is exactly one face width wide (height from its image's aspect), so a mask drawn at the face's proportions covers the face; it turns with head roll like any other anchor. It is valid on every field that takes an anchor — transform.anchor (types 2 and 4 on a face) and face particles' anchor (type 5, which then spawn at the face centre). It is engine level 1 (ENGINE_LEVEL_BY_ANCHOR): it is part of format-v1.0.0 (added before any lens was published or any SDK shipped), so the SDKs' 1.0.0 render it and a lens using it keeps minEngineVersion 1. Example: fixtures/lens/valid/face-mask.json.
scale
transform.anchor
ENGINE_LEVEL_BY_ANCHOR
minEngineVersion
fixtures/lens/valid/face-mask.json
A sprite sheet (sheet, type 4) requires endBehavior: it applies when loop is false (1 hold last frame, 2 hide); when loop is true it is still required but has no effect. Face particles (type 5) require offset — the spawn offset from the anchor point, same shape, range and meaning as transform.offset. See rendering-v1.md for the normative rendering rules (playback, spawn position, draw order, timing, and everything else a renderer needs pinned).
sheet
loop
1
2
offset
transform.offset
rendering-v1.md
An optional top-level animations list holds keyframe clips; a lens without it is unchanged. A clip animates one layer over its own time, on lens time; the rendering rules are rendering-v1.md §11, which uses the easing formulas and the clip clock of effect-rendering-v1.md E10.1–E10.3.
animations
effect-rendering-v1.md
{ "id": "hat-pop", "layer": "hat", "durationMs": 1000, "loop": false, "autoplay": true, "startMs": 200, "endBehavior": 1, "tracks": [ { "property": 3, "keys": [{ "atMs": 0, "value": 0.3, "easing": 7 }, { "atMs": 1000, "value": 0.6, "easing": 1 }] } ] }
id
layer
autoplay
startMs
tracks
keys
atMs
{ "type": 6, "animation": "<clip id>" }
Properties each layer may animate, and each property's key range (ids 1–5 mean what they mean for screen effects, in the layer's own units):
transform.offset.x
placement.transform.offset.x
offset.x
transform.offset.y
placement.transform.offset.y
offset.y
transform.scale
placement.transform.scale
transform.rotationDeg
placement.transform.rotationDeg
opacity
intensity
The anchor and followRoll never animate. Tables in code: lensAnimatableProperties(layer), LENS_ANIMATION_VALUE_RANGE, MAX_ANIMATIONS (16), MAX_TRACKS_PER_ANIMATION (6), MAX_KEYS_PER_TRACK (32); the easings are AnimationEasing, shared with screen effects. Every animation feature is engine level 1. Example: fixtures/lens/valid/animated.json animates every property on every layer kind that may, with every easing, and starts two clips from a mouth.open trigger.
followRoll
lensAnimatableProperties(layer)
LENS_ANIMATION_VALUE_RANGE
MAX_ANIMATIONS
MAX_TRACKS_PER_ANIMATION
MAX_KEYS_PER_TRACK
AnimationEasing
fixtures/lens/valid/animated.json
packages/format/schema/lens.v1.schema.json
packages/format/fixtures/lens/valid/
fixtures/lens/invalid/
{ "description": "...", "base": "face-crown", "patch": [{ "op": "replace", "path": "/triggers/0/event/name", "value": "lens.exploded" }], "expectedIssues": [{ "path": "/triggers/0/event/name", "code": "trigger.unknown_event" }] }
base
fixtures/lens/valid/
.json
patch
add
remove
replace
-
expectedIssues
checkRules
path
code
rejectedBySchema: true
schema.*
A JSON Schema check is necessary but not sufficient. Semantic rules run only on a schema-valid manifest — a schema failure is reported as schema issues alone, and the rules below never run against it. For schema failures, conformance means "rejected"; only the path + code of a semantic issue are normative.
layer.duplicate_id
/layers/i/id
asset.missing
/layers/i/asset
asset
assets
asset.unused
/assets/<name>
package.too_large
/assets
assets[*].bytes
>
LENS_PACKAGE_BYTE_CAP
particles.too_many
/layers
maxParticles
trigger.unknown_event
/triggers/i/event/name
effect.started
effect.ended
trigger.threshold_not_applicable
/triggers/i/threshold
trigger.unknown_layer
/triggers/i/actions/j/layer
trigger.action_mismatch
/triggers/i/actions/j
ACTION_TARGET_TYPES[action.type]
trigger.unknown_animation
/triggers/i/actions/j/animation
animation.duplicate_id
/animations/i/id
animation.unknown_layer
/animations/i/layer
animation.property_not_animatable
/animations/i/tracks/j/property
lensAnimatableProperties
placement.mode
animation.duplicate_property
animation.keys_out_of_order
/animations/i/tracks/j/keys/k/atMs
animation.key_outside_clip
animation.value_out_of_range
/animations/i/tracks/j/keys/k/value
animation.autoplay_conflict
/animations/i/autoplay
engine.too_low
/minEngineVersion
ENGINE_LEVEL_BY_LAYER_TYPE
ENGINE_LEVEL_BY_EVENT_SOURCE
constants.ts
Notes for implementers:
MAX_TEXTURE_DIMENSION
sheet.frameWidth
sheet.frameHeight
asset.sheet_too_small
frameCount
floor(width / frameWidth) × floor(height / frameHeight)
lenses
docs/api/lenses.md
formatVersion changes only on a breaking change to this document. Adding a layer type, an anchor or an event is not breaking: it raises that feature's engine level, and older engines refuse lenses whose minEngineVersion they do not meet. A renderer must therefore compare minEngineVersion with its own engine version before it validates the manifest strictly: an engine-1 renderer that parsed a lens using a later anchor first would report "invalid anchor" instead of "engine too old".
formatVersion
Tags. The SDKs pin this package by the git tag format-v1.0.0. Until the first lens is published and the SDKs' 1.0.0 ship, every v1 change is part of format-v1.0.0: the tag is moved to the commit that makes the change, and the SDKs re-pin it. There is no v1.0.1 or v1.1.0.