
Research
SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.
apexify.js
Advanced tools
šØ Advanced Canvas Rendering Library - Professional image processing, shape drawing, text effects, patterns, filters, and charts. Built with TypeScript & Rust for high performance.
š One Library. Infinite Possibilities. Professional Results.
The most powerful, versatile canvas rendering library for Node.js, Next.js, Discord bots, and beyond.
Create stunning visuals, generate images on-the-fly, build design tools, and power your applications with professional-grade canvas rendering and image processing.
ApexPainter is not just another canvas library - it's a complete visual creation toolkit that works everywhere:
One library. Every use case. Zero compromises.
Generate images in your Next.js app - both client and server-side. Perfect for:
// Next.js API Route Example
// app/api/generate-image/route.ts
import { ApexPainter } from 'apexify.js';
import { NextResponse } from 'next/server';
export async function GET() {
const painter = new ApexPainter();
const canvas = await painter.createCanvas({
width: 1200,
height: 630,
gradientBg: {
type: 'linear',
colors: [
{ stop: 0, color: '#667EEA' },
{ stop: 1, color: '#764BA2' }
]
}
});
return new NextResponse(canvas.buffer, {
headers: { 'Content-Type': 'image/png' }
});
}
Create stunning visuals for your Discord bot:
// Discord Bot Example (discord.js)
import { ApexPainter } from 'apexify.js';
import { AttachmentBuilder } from 'discord.js';
const painter = new ApexPainter();
const canvas = await painter.createCanvas({
width: 800,
height: 400,
customBg: { source: userAvatar },
// Add welcome text, decorations, etc.
});
const attachment = new AttachmentBuilder(canvas.buffer, { name: 'welcome.png' });
await channel.send({ files: [attachment] });
Build powerful design applications:
// Design Tool with Code Export
const design = {
canvas: { width: 1920, height: 1080, colorBg: '#ffffff' },
elements: [
{ type: 'shape', source: 'rectangle', x: 100, y: 100, /* ... */ },
{ type: 'text', text: 'Hello World', x: 200, y: 200, /* ... */ }
]
};
// Generate code from design
const code = generateApexPainterCode(design);
// Export as: await painter.createCanvas(...)
Power your websites with dynamic image generation:
Create professional marketing materials:
Generate game assets and UI elements:
# npm
npm install apexify.js
# yarn
yarn add apexify.js
# pnpm
pnpm add apexify.js
ā
Node.js - Full support (v16+)
ā
Next.js - Frontend & Backend (App Router & Pages Router)
ā
Discord.js - Perfect for Discord bots
ā
Express.js - API endpoints and server-side rendering
ā
Serverless - AWS Lambda, Vercel, Netlify Functions
ā
Docker - Containerized applications
ā
Edge Runtime - Vercel Edge, Cloudflare Workers (with limitations)
import { ApexPainter } from 'apexify';
const painter = new ApexPainter();
// Create a canvas with gradient background
const canvas = await painter.createCanvas({
width: 800,
height: 600,
gradientBg: {
type: 'linear',
colors: [
{ color: '#FF6B6B', position: 0 },
{ color: '#4ECDC4', position: 0.5 },
{ color: '#45B7D1', position: 1 }
],
direction: { x1: 0, y1: 0, x2: 800, y2: 600 }
},
shadow: {
color: '#000',
offsetX: 10,
offsetY: 10,
blur: 20
},
borderRadius: 20
});
// Add a beautiful heart shape (single object)
const heartImage = await painter.createImage({
source: 'heart',
x: 300,
y: 200,
width: 200,
height: 200,
shape: {
fill: true,
gradient: {
type: 'radial',
colors: [
{ color: '#FF6B6B', position: 0 },
{ color: '#FF1744', position: 1 }
],
center: { x: 100, y: 100 },
radius: 100
}
},
shadow: {
color: '#000',
offsetX: 15,
offsetY: 15,
blur: 25
},
stroke: {
color: '#FFF',
width: 5
}
}, canvas.buffer);
// Add stunning text (single object)
const textImage = await painter.createText({
text: 'ApexPainter',
x: 400,
y: 450,
fontSize: 48,
fontFamily: 'Arial',
bold: true,
gradient: {
type: 'linear',
colors: [
{ color: '#FFD700', position: 0 },
{ color: '#FF6B6B', position: 1 }
],
direction: { x1: 0, y1: 0, x2: 300, y2: 0 }
},
glow: {
color: '#FFD700',
intensity: 0.8,
opacity: 0.9
},
shadow: {
color: '#000',
offsetX: 8,
offsetY: 8,
blur: 15
},
stroke: {
color: '#FFF',
width: 3
}
}, heartImage);
// Save the result using the advanced save method
const saveResult = await painter.save(textImage, {
directory: './output',
filename: 'beautiful-artwork',
format: 'png'
});
console.log(`Saved to: ${saveResult.path} (${saveResult.size} bytes)`);
// Or use the simple approach (auto-generated filename)
const autoSave = await painter.save(textImage);
// Saves to: ./ApexPainter_output/20241220_143025_123.png
The save() method provides powerful file saving capabilities with extensive customization:
// Simple save with auto-generated timestamp name
const canvas = await painter.createCanvas({ width: 800, height: 600 });
const result = await painter.save(canvas.buffer);
// Saves to: ./ApexPainter_output/20241220_143025_123.png
// Custom filename and directory
await painter.save(canvas.buffer, {
directory: './my-images',
filename: 'my-canvas',
format: 'jpg',
quality: 95
});
// Save with counter naming
await painter.save(canvas.buffer, {
naming: 'counter',
prefix: 'image-',
counterStart: 1
});
// Saves to: ./ApexPainter_output/image-1.png, image-2.png, etc.
// Save multiple buffers in batch
const buffers = [canvas1.buffer, canvas2.buffer, canvas3.buffer];
const results = await painter.saveMultiple(buffers, {
prefix: 'batch-',
naming: 'counter'
});
// Saves: batch-1.png, batch-2.png, batch-3.png
Save Options:
directory - Output directory (default: ./ApexPainter_output)filename - Custom filename (auto-generated if not provided)format - File format: png, jpg, jpeg, webp, avif, gif (default: png)quality - Quality for JPEG/WebP (0-100, default: 90)naming - Naming pattern: timestamp, counter, or custom (default: timestamp)prefix / suffix - Add prefix/suffix to filenamesoverwrite - Overwrite existing files (default: false, auto-renames if exists)createDirectory - Auto-create directory if missing (default: true)Both createImage() and createText() methods accept single objects OR arrays of objects:
// ā
Single Object
await painter.createImage({
source: 'heart',
x: 100, y: 100,
width: 200, height: 200,
shape: { fill: true, color: '#ff6b6b' }
}, canvasBuffer);
// ā
Array of Objects
await painter.createImage([
{
source: 'rectangle',
x: 50, y: 50,
width: 100, height: 80,
shape: { fill: true, color: '#ff6b6b' }
},
{
source: 'circle',
x: 200, y: 50,
width: 80, height: 80,
shape: { fill: true, color: '#4ecdc4' }
},
{
source: 'star',
x: 350, y: 50,
width: 80, height: 80,
shape: { fill: true, color: '#45b7d1' }
}
], canvasBuffer);
// ā
Single Text Object
await painter.createText({
text: 'Hello World',
x: 100, y: 100,
fontSize: 24,
color: '#ff6b6b'
}, canvasBuffer);
// ā
Array of Text Objects
await painter.createText([
{
text: 'Title',
x: 100, y: 50,
fontSize: 32,
bold: true,
color: '#2c3e50'
},
{
text: 'Subtitle',
x: 100, y: 100,
fontSize: 18,
color: '#666'
},
{
text: 'Body text with effects',
x: 100, y: 150,
fontSize: 14,
color: '#333',
glow: { color: '#ffd700', intensity: 0.5 },
shadow: { color: '#000', offsetX: 2, offsetY: 2, blur: 4 }
}
], canvasBuffer);
All stroke properties now support 6 different stroke styles:
// ā
Basic Stroke Styles
await painter.createImage({
source: 'rectangle',
x: 100, y: 100,
width: 200, height: 150,
shape: { fill: true, color: '#ffffff' },
stroke: {
color: '#ff6b6b',
width: 8,
style: 'dashed' // solid, dashed, dotted, groove, ridge, double
}
}, canvasBuffer);
// ā
Gradient Strokes with Styles
await painter.createImage({
source: 'circle',
x: 200, y: 200,
width: 150, height: 150,
shape: { fill: true, color: '#ffffff' },
stroke: {
gradient: {
type: 'linear',
colors: [
{ stop: 0, color: '#ff6b6b' },
{ stop: 1, color: '#4ecdc4' }
]
},
width: 6,
style: 'ridge' // Works with all styles!
}
}, canvasBuffer);
// ā
Text Strokes with Styles
await painter.createText({
text: 'Styled Text',
x: 100, y: 100,
fontSize: 32,
color: '#ffffff',
stroke: {
color: '#ff6b6b',
width: 4,
style: 'double' // All 6 styles supported!
}
}, canvasBuffer);
Available Stroke Styles:
solid - Clean solid line (default)dashed - Dashed line patterndotted - Dotted line patterngroove - 3D grooved effect (dark outer, light inner)ridge - 3D ridged effect (light outer, dark inner)double - Double parallel linesText fonts are now organized in a clean font object structure:
// ā
New Font Object Structure
await painter.createText({
text: 'Organized Font',
x: 100, y: 100,
font: {
size: 24, // Font size in pixels
family: 'Arial', // Font family name
name: 'customFont', // Custom font name (for registration)
path: './fonts/custom.ttf' // Path to custom font file
},
color: '#333333',
bold: true,
italic: true
}, canvasBuffer);
// ā
Backward Compatibility (Legacy Properties)
await painter.createText({
text: 'Legacy Font Properties',
x: 100, y: 150,
fontSize: 24, // Still works!
fontFamily: 'Arial', // Still works!
fontName: 'customFont', // Still works!
fontPath: './fonts/custom.ttf', // Still works!
color: '#333333'
}, canvasBuffer);
// ā
Mixed Usage (New Object Takes Priority)
await painter.createText({
text: 'Mixed Usage',
x: 100, y: 200,
font: {
size: 28,
family: 'Georgia'
},
fontSize: 24, // Ignored (font.size takes priority)
fontFamily: 'Arial', // Ignored (font.family takes priority)
color: '#333333'
}, canvasBuffer);
Font Object Properties:
size - Font size in pixels (replaces fontSize)family - Font family name (replaces fontFamily)name - Custom font name for registration (replaces fontName)path - Path to custom font file (replaces fontPath)Benefits:
font object overrides legacy propertiesText effects now support gradients for enhanced visual appeal:
// ā
Gradient Glow
await painter.createText({
text: 'Gradient Glow Text',
x: 100, y: 100,
fontSize: 32,
color: '#ffffff',
glow: {
gradient: {
type: 'linear',
colors: [
{ stop: 0, color: '#ff6b6b' },
{ stop: 1, color: '#4ecdc4' }
]
},
intensity: 15,
opacity: 0.9
}
}, canvasBuffer);
// ā
Gradient Highlight
await painter.createText({
text: 'Gradient Highlight',
x: 100, y: 150,
fontSize: 24,
color: '#000000',
highlight: {
gradient: {
type: 'radial',
colors: [
{ stop: 0, color: '#ffd700' },
{ stop: 1, color: '#ff6b6b' }
]
},
opacity: 0.6
}
}, canvasBuffer);
// ā
Gradient Text Decorations
await painter.createText({
text: 'Styled Decorations',
x: 100, y: 200,
fontSize: 28,
color: '#ffffff',
underline: {
gradient: {
type: 'linear',
colors: [
{ stop: 0, color: '#ff6b6b' },
{ stop: 1, color: '#4ecdc4' }
]
},
width: 4
},
overline: {
gradient: {
type: 'linear',
colors: [
{ stop: 0, color: '#feca57' },
{ stop: 1, color: '#ff9ff3' }
]
},
width: 3
},
strikethrough: {
gradient: {
type: 'linear',
colors: [
{ stop: 0, color: '#96ceb4' },
{ stop: 1, color: '#45b7d1' }
]
},
width: 5
}
}, canvasBuffer);
// ā
Backward Compatibility (Simple Boolean)
await painter.createText({
text: 'Simple Decorations',
x: 100, y: 250,
fontSize: 24,
color: '#ffffff',
underline: true, // Uses default color
overline: true, // Uses default color
strikethrough: true // Uses default color
}, canvasBuffer);
Gradient Support:
boolean values still work// Create a professional business card
const businessCard = await painter.createCanvas({
width: 400,
height: 250,
gradientBg: {
type: 'linear',
colors: [
{ color: '#2C3E50', position: 0 },
{ color: '#34495E', position: 1 }
],
direction: { x1: 0, y1: 0, x2: 400, y2: 250 }
},
patternBg: {
type: 'dots',
color: '#FFF',
opacity: 0.1,
size: 5,
spacing: 20
}
});
// Add company logo (star shape)
const logo = await painter.createImage({
source: 'star',
x: 50,
y: 50,
width: 60,
height: 60,
fill: true,
color: '#FFD700',
shadow: { color: '#000', offsetX: 3, offsetY: 3, blur: 8 }
}, businessCard.buffer);
// Add company name
const companyText = await painter.createText({
text: 'ACME Corp',
x: 130,
y: 80,
fontSize: 24,
fontFamily: 'Arial',
bold: true,
color: '#FFF',
shadow: { color: '#000', offsetX: 2, offsetY: 2, blur: 4 }
}, logo);
// Add contact info
const contactText = await painter.createText({
text: 'john@acme.com\n+1 (555) 123-4567',
x: 50,
y: 150,
fontSize: 14,
fontFamily: 'Arial',
color: '#BDC3C7',
lineHeight: 1.5
}, companyText);
// Create a game button
const gameButton = await painter.createCanvas({
width: 200,
height: 60,
gradientBg: {
type: 'linear',
colors: [
{ color: '#FF6B6B', position: 0 },
{ color: '#FF1744', position: 1 }
],
direction: { x1: 0, y1: 0, x2: 200, y2: 60 }
},
shadow: {
color: '#000',
offsetX: 5,
offsetY: 5,
blur: 15
},
borderRadius: 30
});
// Add button text with glow effect
const buttonText = await painter.createText({
text: 'PLAY NOW',
x: 100,
y: 35,
fontSize: 20,
fontFamily: 'Arial',
bold: true,
color: '#FFF',
textAlign: 'center',
textBaseline: 'middle',
glow: {
color: '#FFD700',
intensity: 0.6,
opacity: 0.8
},
shadow: {
color: '#000',
offsetX: 2,
offsetY: 2,
blur: 4
}
}, gameButton.buffer);
Both createImage() and createText() methods accept:
ImageProperties or TextPropertiesImageProperties[] or TextProperties[]This allows you to add multiple elements in one call for better performance and cleaner code.
rectangle - Standard rectanglesquare - Perfect squarecircle - Perfect circletriangle - Equilateral triangletrapezium - Trapezoid shapestar - 5-pointed starheart - Heart shape with bezier curvespolygon - Custom polygongrid - Grid patterndots - Dot patterndiagonal - Diagonal linesstripes - Horizontal/vertical stripeswaves - Wave patterncrosses - Cross patternhexagons - Hexagonal patterncheckerboard - Checkerboard patterndiamonds - Diamond patterntriangles - Triangle patternstars - Star patternpolka - Polka dot patterncustom - Custom image patterncreateVideo() methodSee CHANGELOG.md for complete details.
// app/api/og-image/route.ts
import { ApexPainter } from 'apexify.js';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const title = searchParams.get('title') || 'Default Title';
const painter = new ApexPainter();
const canvas = await painter.createCanvas({
width: 1200,
height: 630,
gradientBg: {
type: 'linear',
colors: [
{ stop: 0, color: '#667EEA' },
{ stop: 1, color: '#764BA2' }
]
}
});
const image = await painter.createText({
text: title,
x: 600,
y: 315,
fontSize: 72,
bold: true,
color: '#FFFFFF',
textAlign: 'center'
}, canvas);
return new NextResponse(image, {
headers: { 'Content-Type': 'image/png' }
});
}
// discord-bot.ts
import { ApexPainter } from 'apexify.js';
import { Client, GatewayIntentBits, AttachmentBuilder } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.GuildMembers] });
const painter = new ApexPainter();
client.on('guildMemberAdd', async (member) => {
// Create welcome card
const canvas = await painter.createCanvas({
width: 1024,
height: 500,
customBg: { source: './assets/welcome-bg.jpg' }
});
const image = await painter.createText([
{
text: `Welcome ${member.user.username}!`,
x: 512,
y: 200,
fontSize: 48,
bold: true,
color: '#FFFFFF',
textAlign: 'center'
},
{
text: `You are member #${member.guild.memberCount}`,
x: 512,
y: 280,
fontSize: 32,
color: '#CCCCCC',
textAlign: 'center'
}
], canvas);
const attachment = new AttachmentBuilder(image, { name: 'welcome.png' });
await member.guild.systemChannel?.send({ files: [attachment] });
});
// design-editor.ts
import { ApexPainter } from 'apexify.js';
class DesignEditor {
private painter = new ApexPainter();
private design: any = { elements: [] };
// Add element to design
addElement(element: any) {
this.design.elements.push(element);
}
// Render design
async render() {
const canvas = await this.painter.createCanvas(this.design.canvas);
let buffer = canvas.buffer;
for (const element of this.design.elements) {
if (element.type === 'shape') {
buffer = await this.painter.createImage(element, buffer);
} else if (element.type === 'text') {
buffer = await this.painter.createText(element, buffer);
}
}
return buffer;
}
// Export as ApexPainter code
exportCode() {
return `
import { ApexPainter } from 'apexify.js';
const painter = new ApexPainter();
const canvas = await painter.createCanvas(${JSON.stringify(this.design.canvas, null, 2)});
${this.design.elements.map((el: any, i: number) =>
`const step${i} = await painter.${el.type === 'shape' ? 'createImage' : 'createText'}(${JSON.stringify(el, null, 2)}, ${i === 0 ? 'canvas' : `step${i-1}`});`
).join('\n')}
`.trim();
}
}
// analytics-dashboard.ts
import { ApexPainter } from 'apexify.js';
async function generateAnalyticsChart(data: number[]) {
const painter = new ApexPainter();
const chart = await painter.createChart({
chartType: 'bar',
chartNumber: 1,
data: {
chartData: { width: 800, height: 400 },
xLabels: data.map((_, i) => i),
yLabels: [0, 25, 50, 75, 100],
data: {
xAxis: data.map((value, i) => ({
label: `Day ${i + 1}`,
value,
position: { startsXLabel: i * 80, endsXLabel: (i + 1) * 80 }
})),
yAxis: data
}
}
});
return chart;
}
// image-processor.ts
import { ApexPainter } from 'apexify.js';
async function processUserUploads(images: string[]) {
const painter = new ApexPainter();
// Process all images in parallel
const results = await painter.batch(
images.map(image => ({
type: 'image' as const,
config: {
source: image,
x: 0,
y: 0,
filters: [{ type: 'gaussianBlur', radius: 5 }],
borderRadius: 20
}
}))
);
// Save all processed images
await painter.saveMultiple(results, {
directory: './processed',
prefix: 'processed-',
naming: 'counter'
});
return results;
}
Comprehensive video series covering:
We welcome contributions! Here's how you can help:
# Clone the repository
git clone https://github.com/EIAS79/Apexify.js.git
# Install dependencies
npm install
# Run tests
npm test
# Build the project
npm run build
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ā¤ļø by Jedi Studio
Create stunning visuals with ApexPainter - The ultimate canvas library for Node.js
FAQs
šØ Advanced Canvas Rendering Library - Professional image processing, shape drawing, text effects, patterns, filters, and charts. Built with TypeScript & Rust for high performance.
The npm package apexify.js receives a total of 248 weekly downloads. As such, apexify.js popularity was classified as not popular.
We found that apexify.js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.

Company News
Socket is proud to join the OpenJS Foundation as a Silver Member, deepening our commitment to the long-term health and security of the JavaScript ecosystem.

Security News
npm now links to Socket's security analysis on every package page. Here's what you'll find when you click through.