Introduction to relottie
Discover relottie, a powerful ecosystem for developers to programmatically transform, analyze, and manipulate Lottie animations. Learn how to use ASTs and plugins to edit Lottie JSON.
Introduction to relottie
A unified, plugin-based ecosystem for programmatic Lottie animation processing.
relottie is an enterprise-grade toolkit for developers who need to programmatically analyze, transform, and manipulate Lottie animations at scale. Built on the robust unified.js processing engine, relottie transforms Lottie JSON files into Abstract Syntax Trees (ASTs), enabling reliable, structured manipulation through a composable plugin architecture.
What Problems Does relottie Solve?
Working with Lottie animations programmatically presents several challenges:
The Raw JSON Problem
Lottie files are complex, deeply nested JSON structures. Manual manipulation is:
Error-Prone: One typo can break the entire animation
Fragile: Changes to structure require updating all manipulation code
Difficult to Scale: Batch operations across hundreds of files become unwieldy
Hard to Validate: Ensuring modifications maintain Lottie spec compliance is complex
Common Scenarios relottie Addresses
Batch Processing: Update brand colors across 500+ animation files
Dynamic Theming: Programmatically adapt animations to user preferences
Automated QA: Extract and validate animation metadata in CI/CD pipelines
Feature Detection: Identify animations using unsupported features before deployment
Performance Optimization: Analyze and optimize animations for specific platforms
Custom Tooling: Build domain-specific Lottie editors and validators
Integration Workflows: Prepare Lottie files for conversion to
.lottieformat or other systems
The relottie Solution
relottie solves these challenges by:
Structured Representation: Converting JSON to semantic ASTs with Lottie-specific node types
Plugin Ecosystem: Providing reusable, composable transformation units
Type Safety: Full TypeScript support for AST nodes and transformations
Validation: Ensuring transformations produce valid Lottie output
Extensibility: Easy creation of custom plugins for domain-specific needs
Core Concepts
Understanding three foundational concepts is essential to working with relottie:
1. Abstract Syntax Trees (ASTs)
An Abstract Syntax Tree is a tree representation of the structure of your Lottie JSON. Instead of working with raw text or generic JSON objects, relottie parses Lottie files into a semantic tree where each node has a specific type and meaning.
Why ASTs Matter:
Semantic Understanding: Nodes represent Lottie concepts (layers, transforms, colors) not just JSON keys
Reliable Navigation: Tree traversal utilities make finding and modifying content predictable
Transformation Safety: Changes to the tree structure are validated before output
Position Tracking: Every node knows its location in the original source for error reporting
2. LAST (Lottie Abstract Syntax Tree)
LAST is relottie's specific AST specification for representing Lottie animations. It extends the universal unist specification, making it compatible with the broader unified.js ecosystem of hundreds of utilities.
Key LAST Features:
Semantic
titleProperty: Each node has atitledescribing its Lottie meaning (e.g.,layer-image,transform-opacity,version)Type Hierarchy: Nodes are typed as
Root,Attribute,Primitive,Element, orJSONnodesPosition Information: Tracks source location (line, column, offset) for debugging
Expression Detection: Root node flags presence of potentially unsafe JavaScript expressions
TypeScript Types: Full type definitions in
@lottiefiles/lastpackage
Visualization: JSON to LAST Transformation
Lottie JSON LAST Tree
┌─────────────────────┐ ┌─────────────────────────────┐
│ { │ │ Root │
│ "v": "6.0.0", │ ───> │ ├─ Attribute (title: "version")│
│ "w": 512, │ │ │ └─ Primitive ("6.0.0") │
│ "h": 512, │ │ ├─ Attribute (title: "width") │
│ "fr": 60 │ │ │ └─ Primitive (512) │
│ } │ │ ├─ Attribute (title: "height")│
└─────────────────────┘ │ │ └─ Primitive (512) │
│ └─ Attribute (title: "framerate")│
│ └─ Primitive (60) │
└─────────────────────────────┘Each node in the LAST tree:
Has a
typeproperty (root,attribute,primitive, etc.)Contains a semantic
titlederived from Lottie specificationMay have
childrenfor nested structuresIncludes
positioninformation for source mapping
Learn more: Guide: Working with LAST
3. The unified.js Pipeline
relottie is built on unified, a battle-tested content processing engine used by projects like remark (Markdown) and rehype (HTML).
The Processing Pipeline:
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐
│ Lottie │ │ LAST │ │ LAST │ │ Lottie │
│ JSON │ ───> │ Tree │ ───> │ Tree │ ───> │ JSON │
│ (Input) │ │ (Parse) │ │ (Transform) │ │ (Output)│
└─────────┘ └─────────┘ └─────────────┘ └─────────┘
│ │ │ │
│ │ │ │
relottie() relottie-parse Your Plugins relottie-stringify
processor (metadata,
transforms,
analysis)Pipeline Stages:
Parse:
relottie-parseconverts Lottie JSON → LAST treeTransform: Plugins traverse and modify the LAST tree (run sequentially)
Stringify:
relottie-stringifyconverts LAST tree → Lottie JSON
This architecture enables:
Composability: Chain multiple transformations
Reusability: Plugins work across projects
Isolation: Each plugin operates independently
Testability: Test plugins in isolation
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ relottie Ecosystem │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ @lottiefiles/relottie │ │
│ │ (Processor + Pipeline) │ │
│ └─────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴──────────────────────────────────────┐ │
│ │ unified.js │ │
│ │ (Core Processing Engine) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Core Plugins │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ @lottiefiles/relottie-parse (Parser) │ │
│ │ @lottiefiles/relottie-stringify (Compiler) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Utility Plugins │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ @lottiefiles/relottie-metadata │ │
│ │ @lottiefiles/relottie-extract-features │ │
│ │ @lottiefiles/relottie-style (community) │ │
│ │ [Your Custom Plugins] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Support Packages │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ @lottiefiles/last (AST Types) │ │
│ │ @lottiefiles/last-builder (AST Construction) │ │
│ │ @lottiefiles/relottie-cli (Command Line) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ unist Ecosystem Utilities │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ unist-util-visit (Tree Traversal) │ │
│ │ unist-util-filter (Tree Filtering) │ │
│ │ unist-util-map (Tree Mapping) │ │
│ │ unist-util-select (CSS-like Selection) │ │
│ │ + 100+ more utilities │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘Feature Matrix & Capabilities
| Feature | Capability | Package |
| Parse Lottie JSON | Convert JSON to LAST tree | @lottiefiles/relottie-parse |
| Stringify to JSON | Convert LAST tree to JSON | @lottiefiles/relottie-stringify |
| Metadata Extraction | Width, height, framerate, duration, colors | @lottiefiles/relottie-metadata |
| Feature Detection | Identify used Lottie features | @lottiefiles/relottie-extract-features |
| Tree Traversal | Visit, filter, map, select nodes | unist-util-* |
| Custom Transformations | Modify colors, properties, structure | Custom plugins |
| Position Tracking | Source maps for error reporting | Built-in |
| Expression Detection | Security: Flag JS expressions | Built-in |
| TypeScript Support | Full type definitions | All packages |
| CLI Tool | Command-line processing | @lottiefiles/relottie-cli |
| AST Building | Programmatic AST construction | @lottiefiles/last-builder |
| Batch Processing | Process multiple files | Custom implementation |
| Plugin Development | Create custom transformations | Plugin API |
Plugin Ecosystem Overview
Official Plugins
Core (Bundled with relottie):
@lottiefiles/relottie-parse: Parses Lottie JSON into LAST treeBased on Momoa JSON parser
Provides semantic Lottie node titles
Configurable position tracking
@lottiefiles/relottie-stringify: Converts LAST tree back to Lottie JSONProduces valid, formatted output
Configurable indentation and spacing
Preserves or updates structure
Utilities:
@lottiefiles/relottie-metadata: Extracts animation metadataDimensions (width, height)
Timing (framerate, duration, in/out points)
Colors used in animation
Layer counts and types
Attaches to
vfile.data
@lottiefiles/relottie-extract-features: Analyzes feature usageIdentifies all Lottie features used
Useful for compatibility checking
Generates feature report
Attaches to
vfile.data
Community Plugins
@lottiefiles/relottie-style: Style inspection and transformationCustom Plugins: See Ecosystem: Plugins
Creating Custom Plugins
Plugins are functions that return transformers. A minimal plugin:
import type { Plugin } from "unified";
import type { Root } from "@lottiefiles/last";
import { visit } from "unist-util-visit";
const myPlugin: Plugin<[], Root> = () => {
return (tree, file) => {
// Transform the tree
visit(tree, "attribute", (node) => {
// Your logic here
});
};
};Quick Start
Installation
npm install @lottiefiles/relottie @lottiefiles/last unist-util-visitBasic Usage: Update Animation Version
import { relottie } from "@lottiefiles/relottie";
import { TITLES } from "@lottiefiles/last";
import type { Root, Attribute } from "@lottiefiles/last";
import type { Plugin } from "unified";
import { visit, EXIT } from "unist-util-visit";
// Define a plugin to update version
const updateVersion: Plugin<[{ version: string }], Root> = (options) => {
return (tree, file) => {
visit(tree, "attribute", (node: Attribute) => {
// Find the version attribute
if (node.title !== TITLES.string.version) return;
// Update the primitive value
const primitiveNode = node.children[0];
if (primitiveNode && primitiveNode.type === "primitive") {
primitiveNode.value = options.version;
}
return EXIT; // Stop after finding version
});
};
};
// Process a Lottie file
const lottieJson = '{"v":"5.5.2","w":512,"h":512,"fr":60,"ip":0,"op":60,"layers":[]}';
const processor = relottie().use(updateVersion, { version: "6.0.0" });
const result = await processor.process(lottieJson);
console.log(String(result));
// Output: {"v":"6.0.0","w":512,"h":512,"fr":60,"ip":0,"op":60,"layers":[]}Extract Metadata
import { relottie } from "@lottiefiles/relottie";
import relottieMetadata from "@lottiefiles/relottie-metadata";
const processor = relottie().use(relottieMetadata);
const result = await processor.process(lottieJson);
console.log(result.data.lottieMetadata);
// Output:
// {
// width: 512,
// height: 512,
// framerate: 60,
// inPoint: 0,
// outPoint: 60,
// duration: 1.0,
// colors: [...],
// ...
// }Detect Features
import { relottie } from "@lottiefiles/relottie";
import relottieExtractFeatures from "@lottiefiles/relottie-extract-features";
const processor = relottie().use(relottieExtractFeatures);
const result = await processor.process(lottieJson);
console.log(result.data.lottieFeatures);
// Output: ['layers', 'shapes', 'transforms', ...]Common Use Cases
1. Batch Color Replacement
Replace all instances of a specific color across animations:
import { visit } from "unist-util-visit";
import type { Plugin } from "unified";
import type { Root, Primitive } from "@lottiefiles/last";
interface ColorOptions {
from: [number, number, number, number]; // RGBA
to: [number, number, number, number];
}
const replaceColor: Plugin<[ColorOptions], Root> = (options) => {
return (tree) => {
visit(tree, "element", (node) => {
// Check if this is a color array [r, g, b, a]
if (node.children.length === 4 && node.children.every((child) => child.type === "primitive")) {
const values = node.children.map((child) => (child as Primitive).value);
// Compare colors
if (colorsMatch(values, options.from)) {
// Replace with new color
node.children.forEach((child, i) => {
(child as Primitive).value = options.to[i];
});
}
}
});
};
};
function colorsMatch(a: any[], b: number[]): boolean {
return a.length === 4 && a.every((val, i) => Math.abs(val - b[i]) < 0.01);
}
// Usage
const processor = relottie().use(replaceColor, {
from: [1, 0, 0, 1], // Red
to: [0, 0, 1, 1], // Blue
});2. Remove Expressions for Security
Strip potentially unsafe JavaScript expressions:
const removeExpressions: Plugin<[], Root> = () => {
return (tree, file) => {
let removed = 0;
visit(tree, "attribute", (node, index, parent) => {
if (node.title === "expression") {
// Remove expression node
if (parent && index !== undefined) {
parent.children.splice(index, 1);
removed++;
return index; // Continue from current index
}
}
});
file.message(`Removed ${removed} expressions`, tree);
};
};3. Optimize Animation Size
Remove unused or redundant properties:
const optimizeSize: Plugin<[], Root> = () => {
return (tree) => {
visit(tree, "attribute", (node, index, parent) => {
// Remove attributes with default values
if (node.title === "autoOrient" && node.children[0]?.type === "primitive" && node.children[0].value === 0) {
// Remove default autoOrient
if (parent && index !== undefined) {
parent.children.splice(index, 1);
return index;
}
}
});
};
};4. Validate Animation Properties
Ensure animations meet specific requirements:
const validateAnimation: Plugin<[], Root> = () => {
return (tree, file) => {
let width: number | undefined;
let height: number | undefined;
let framerate: number | undefined;
visit(tree, "attribute", (node) => {
if (node.title === TITLES.number.width) {
width = node.children[0]?.value;
} else if (node.title === TITLES.number.height) {
height = node.children[0]?.value;
} else if (node.title === TITLES.number.framerate) {
framerate = node.children[0]?.value;
}
});
// Validate
if (!width || !height) {
file.fail("Animation must have width and height", tree);
}
if (width && width > 2048) {
file.message("Width exceeds recommended maximum (2048px)", tree);
}
if (framerate && framerate > 60) {
file.message("Framerate exceeds 60fps", tree);
}
};
};5. Extract Layer Information
Build an inventory of all layers:
interface LayerInfo {
type: string;
name?: string;
index: number;
}
const extractLayers: Plugin<[], Root> = () => {
return (tree, file) => {
const layers: LayerInfo[] = [];
visit(tree, "attribute", (node) => {
if (node.title === "layer-type") {
const typeNode = node.children[0];
if (typeNode?.type === "primitive") {
layers.push({
type: String(typeNode.value),
index: layers.length,
});
}
}
});
file.data.layers = layers;
};
};Tool Comparison
relottie vs dotlottie-js vs Manual Editing
| Aspect | relottie | dotlottie-js | Manual JSON Editing |
| Primary Purpose | Programmatic analysis & transformation | Create .lottie bundles | Quick one-off edits |
| Use Case | Batch processing, automation, tooling | Package animations for distribution | Simple value changes |
| Input Format | Lottie JSON | Lottie JSON + assets | Lottie JSON |
| Output Format | Lottie JSON | .lottie binary format | Lottie JSON |
| Transformation | Full AST-based manipulation | Limited (bundling focus) | Manual find/replace |
| Type Safety | Full TypeScript support | TypeScript API | None |
| Validation | AST structure validation | Format validation | None |
| Extensibility | Plugin ecosystem | Fixed feature set | N/A |
| Scale | Hundreds/thousands of files | Individual conversions | Single files |
| Learning Curve | Moderate (AST concepts) | Low | Very low |
| Performance | Fast (optimized traversal) | Fast | Instant (no processing) |
| Error Handling | Detailed position info | Standard errors | No validation |
| Best For | Automation, CI/CD, tooling | Distribution, app bundling | Quick fixes |
When to Use Each Tool
Use relottie when you need to:
Batch process multiple animations
Perform complex transformations
Build custom Lottie tooling
Analyze animation properties
Validate Lottie files in CI/CD
Create reusable transformation logic
Extract metadata programmatically
Use dotlottie-js when you need to:
Package animations with assets into .lottie format
Prepare animations for app distribution
Bundle multiple animations together
Optimize delivery size with compression
Use manual editing when:
Making a single quick change
Testing animation behavior
Learning Lottie format
No automation required
Combine tools:
Use relottie to prepare/transform Lottie JSON
Use dotlottie-js to package the result into .lottie format
Example: Optimize colors with relottie, bundle with dotlottie-js
When to Use relottie
Ideal Scenarios
✅ Automation & Scale
Processing 100+ animation files
CI/CD integration for animation validation
Automated color/brand updates
✅ Analysis & Reporting
Extract metadata from animation libraries
Feature compatibility checking
Performance analysis
✅ Custom Tooling
Building Lottie-specific editors
Creating domain-specific validators
Developing animation optimization tools
✅ Complex Transformations
Multi-step modifications
Conditional transformations based on content
Cross-file consistency enforcement
Consider Alternatives When
❌ Simple, one-time changes - Manual editing may be faster
❌ Need .lottie format output - Use dotlottie-js directly (or after relottie)
❌ Runtime manipulation - Consider Lottie player APIs instead
❌ No programming required - Use GUI tools like LottieFiles editor
Performance Characteristics
Processing Speed
Parsing: ~2-5ms for typical animations (< 1MB JSON)
Transformation: Depends on plugin complexity, typically < 10ms
Stringification: ~1-3ms for typical animations
Total Pipeline: ~5-20ms per file for standard workflows
Memory Usage
AST Overhead: ~2-3x source JSON size in memory
Streaming: Not currently supported (loads full tree)
Recommended: Process files individually for large batches
Optimization Tips
// ✅ Efficient: Single processor, reused
const processor = relottie().use(plugin1).use(plugin2);
for (const file of files) {
await processor.process(file);
}
// ❌ Inefficient: Creating new processor each time
for (const file of files) {
const processor = relottie().use(plugin1).use(plugin2);
await processor.process(file);
}Benchmarks
Typical performance on M1 MacBook Pro:
100 small animations (< 50KB): ~1-2 seconds
100 medium animations (< 500KB): ~5-10 seconds
100 large animations (> 1MB): ~20-30 seconds
Note: Actual performance varies based on plugin complexity and animation structure.
Next Steps
Learn the Fundamentals
Core Concepts: Deep dive into ASTs, LAST, and the processing pipeline
What is relottie?: Understand the philosophy and architecture
Why use relottie?: Explore use cases and benefits
Start Building
Quick Start: Build your first transformation
Usage Guide: Practical examples and patterns
Using the CLI: Command-line workflows
Advanced Topics
Working with LAST: Master the AST structure
Creating Plugins: Build custom transformations
Modifying Lottie Files: Complex transformation patterns
Analyzing Lottie Files: Extract insights from animations
API Reference
Processor API: Core processor methods
Parse Plugin: Parser configuration
Stringify Plugin: Output formatting
Metadata Plugin: Extract animation metadata
Extract Features Plugin: Feature detection
LAST Specification: Complete AST reference
Community & Support
GitHub Repository: Source code and issue tracking
Plugin Ecosystem: Discover and share plugins
Security Considerations: Best practices for safe usage
We're excited to see what you build with relottie! Whether you're automating animation workflows, building custom tooling, or analyzing animation libraries at scale, relottie provides the foundation for reliable, maintainable Lottie processing.