diff --git a/app/api/icon/README.md b/app/api/icon/README.md
new file mode 100644
index 00000000..2f9a71fe
--- /dev/null
+++ b/app/api/icon/README.md
@@ -0,0 +1,127 @@
+# Icon Generator API
+
+This API endpoint generates customizable icon backgrounds programmatically. It accepts query parameters to customize the icon appearance and returns an SVG image.
+
+## Endpoint
+
+```
+GET /api/icon
+```
+
+## Query Parameters
+
+### Background Fill
+
+- `backgroundFillType` - Fill type: `Solid`, `Linear`, or `Radial` (default: `Linear`)
+- `backgroundStartColor` - Primary color in hex format (default: `#FF6363`)
+- `backgroundEndColor` - Secondary color for gradients (default: `#FFA07A`)
+- `backgroundAngle` - Gradient angle in degrees for Linear fill (default: `45`)
+- `backgroundPosition` - Position for Radial gradient as `x%,y%` (default: `50%,50%`)
+- `backgroundSpread` - Spread percentage for Radial gradient (default: `80`)
+
+### Background Style
+
+- `backgroundRadius` - Corner radius in pixels (default: `128`)
+- `backgroundStrokeSize` - Border width in pixels (default: `0`)
+- `backgroundStrokeColor` - Border color in hex format (default: `#000000`)
+- `backgroundStrokeOpacity` - Border opacity 0-100 (default: `100`)
+- `backgroundRadialGlare` - Enable radial glare effect: `true` or `false` (default: `false`)
+
+### Icon
+
+- `icon` - Icon name from Raycast icons (default: `Dots`)
+ - Use kebab-case names like `airplane`, `star`, `folder`, etc.
+ - See available icons at https://www.raycast.com/icons
+- `iconColor` - Icon color in hex format (default: `#FFFFFF`)
+- `iconSize` - Icon size in pixels (default: `256`)
+- `iconOffsetX` - Horizontal offset in pixels (default: `0`)
+- `iconOffsetY` - Vertical offset in pixels (default: `0`)
+
+### Output
+
+- `size` - Output size in pixels (default: `512`)
+
+## Examples
+
+### Basic Icon with Gradient
+
+```bash
+curl "http://localhost:3000/api/icon?icon=airplane&backgroundStartColor=%23FF6363&backgroundEndColor=%23FFA07A"
+```
+
+### Solid Color Background
+
+```bash
+curl "http://localhost:3000/api/icon?icon=star&backgroundFillType=Solid&backgroundStartColor=%234A90E2&size=256"
+```
+
+### Radial Gradient
+
+```bash
+curl "http://localhost:3000/api/icon?icon=folder&backgroundFillType=Radial&backgroundStartColor=%23FF6B6B&backgroundEndColor=%234ECDC4&backgroundSpread=60"
+```
+
+### With Border and Glare
+
+```bash
+curl "http://localhost:3000/api/icon?icon=heart&backgroundRadialGlare=true&backgroundStrokeSize=8&backgroundStrokeColor=%23FFFFFF&backgroundStrokeOpacity=50"
+```
+
+## Response
+
+The API returns an SVG image with `Content-Type: image/svg+xml`.
+
+## Error Responses
+
+### Invalid Icon
+
+```json
+{
+ "error": "Icon \"invalid-name\" not found",
+ "availableIcons": ["add-person", "airplane", "..."],
+ "totalIcons": 603
+}
+```
+
+### Server Error
+
+```json
+{
+ "error": "Failed to generate icon",
+ "details": "Error message"
+}
+```
+
+## Notes
+
+- Icon rendering uses a placeholder circle. For full icon rendering with actual Raycast icon paths, use the web interface at `/icon`
+- All color values should be URL-encoded (e.g., `#FF6363` becomes `%23FF6363`)
+- The API includes aggressive caching headers for performance
+- SVG output can be easily converted to PNG using tools like ImageMagick or browser APIs
+
+## Integration Example
+
+### HTML
+
+```html
+
+```
+
+### JavaScript
+
+```javascript
+const iconUrl = new URL("/api/icon", window.location.origin);
+iconUrl.searchParams.set("icon", "star");
+iconUrl.searchParams.set("backgroundStartColor", "#4A90E2");
+iconUrl.searchParams.set("size", "512");
+
+fetch(iconUrl)
+ .then((response) => response.text())
+ .then((svg) => {
+ // Use the SVG
+ document.getElementById("icon-container").innerHTML = svg;
+ });
+```
diff --git a/app/api/icon/route.ts b/app/api/icon/route.ts
new file mode 100644
index 00000000..3aa78832
--- /dev/null
+++ b/app/api/icon/route.ts
@@ -0,0 +1,211 @@
+import { NextRequest, NextResponse } from "next/server";
+import { SettingsType } from "@icon/lib/types";
+import { Icons, IconName } from "@raycast/icons";
+
+// Default settings for icon generation
+const DEFAULT_SETTINGS: SettingsType = {
+ backgroundFillType: "Linear",
+ backgroundStartColor: "#FF6363",
+ backgroundEndColor: "#FFA07A",
+ backgroundAngle: 45,
+ backgroundPosition: "50%,50%",
+ backgroundSpread: 80,
+ backgroundRadius: 128,
+ backgroundStrokeSize: 0,
+ backgroundStrokeColor: "#000000",
+ backgroundStrokeOpacity: 100,
+ backgroundRadialGlare: false,
+ backgroundNoiseTexture: false,
+ backgroundNoiseTextureOpacity: 50,
+ iconColor: "#FFFFFF",
+ iconSize: 256,
+ iconOffsetX: 0,
+ iconOffsetY: 0,
+ icon: "Raycast" as IconName,
+ fileName: "icon",
+ selectedPresetIndex: null,
+};
+
+function parseQueryParam(value: string | null, defaultValue: any, type: "string" | "number" | "boolean" = "string") {
+ if (value === null) return defaultValue;
+
+ if (type === "number") {
+ const parsed = parseFloat(value);
+ return isNaN(parsed) ? defaultValue : parsed;
+ }
+
+ if (type === "boolean") {
+ return value === "true" || value === "1";
+ }
+
+ return value;
+}
+
+function generateSVG(settings: SettingsType, size: number, iconName: string): string {
+ const strokeSize = settings.backgroundStrokeSize;
+ const strokeWidth = isNaN(parseInt(strokeSize.toString())) ? 0 : parseInt(strokeSize.toString());
+
+ const rectId = `rect-${Date.now()}`;
+ const gradientId = `gradient-${Date.now()}`;
+ const radialGlareGradientId = `radial-glare-${Date.now()}`;
+ const gradientX = settings.backgroundPosition?.split(",")[0] || "50%";
+ const gradientY = settings.backgroundPosition?.split(",")[1] || "50%";
+
+ // Get icon SVG content
+ const IconComponent = Icons[iconName as keyof typeof Icons];
+ let iconSvg = "";
+
+ if (IconComponent) {
+ // Create a temporary SVG to extract the icon path
+ const tempDiv = { __html: "" };
+ try {
+ // Most Raycast icons are simple SVG paths, we'll use a placeholder approach
+ iconSvg = `
+
+ `;
+ } catch (e) {
+ // Fallback to a simple circle
+ iconSvg = ``;
+ }
+ }
+
+ // Build gradient definition
+ let gradientDef = "";
+ if (settings.backgroundFillType === "Radial") {
+ gradientDef = `
+
+
+
+ `;
+ } else if (settings.backgroundFillType === "Linear") {
+ gradientDef = `
+
+
+
+ `;
+ }
+
+ const radialGlareDef = `
+
+
+
+ `;
+
+ const fillValue = settings.backgroundFillType === "Solid" ? settings.backgroundStartColor : `url(#${gradientId})`;
+
+ return ``;
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url);
+
+ // Parse all settings from query parameters
+ const settings: SettingsType = {
+ backgroundFillType: parseQueryParam(searchParams.get("backgroundFillType"), DEFAULT_SETTINGS.backgroundFillType),
+ backgroundStartColor: parseQueryParam(
+ searchParams.get("backgroundStartColor"),
+ DEFAULT_SETTINGS.backgroundStartColor,
+ ),
+ backgroundEndColor: parseQueryParam(searchParams.get("backgroundEndColor"), DEFAULT_SETTINGS.backgroundEndColor),
+ backgroundAngle: parseQueryParam(searchParams.get("backgroundAngle"), DEFAULT_SETTINGS.backgroundAngle, "number"),
+ backgroundPosition: parseQueryParam(searchParams.get("backgroundPosition"), DEFAULT_SETTINGS.backgroundPosition),
+ backgroundSpread: parseQueryParam(
+ searchParams.get("backgroundSpread"),
+ DEFAULT_SETTINGS.backgroundSpread,
+ "number",
+ ),
+ backgroundRadius: parseQueryParam(
+ searchParams.get("backgroundRadius"),
+ DEFAULT_SETTINGS.backgroundRadius,
+ "number",
+ ),
+ backgroundStrokeSize: parseQueryParam(
+ searchParams.get("backgroundStrokeSize"),
+ DEFAULT_SETTINGS.backgroundStrokeSize,
+ "number",
+ ),
+ backgroundStrokeColor: parseQueryParam(
+ searchParams.get("backgroundStrokeColor"),
+ DEFAULT_SETTINGS.backgroundStrokeColor,
+ ),
+ backgroundStrokeOpacity: parseQueryParam(
+ searchParams.get("backgroundStrokeOpacity"),
+ DEFAULT_SETTINGS.backgroundStrokeOpacity,
+ "number",
+ ),
+ backgroundRadialGlare: parseQueryParam(
+ searchParams.get("backgroundRadialGlare"),
+ DEFAULT_SETTINGS.backgroundRadialGlare,
+ "boolean",
+ ),
+ backgroundNoiseTexture: parseQueryParam(
+ searchParams.get("backgroundNoiseTexture"),
+ DEFAULT_SETTINGS.backgroundNoiseTexture,
+ "boolean",
+ ),
+ backgroundNoiseTextureOpacity: parseQueryParam(
+ searchParams.get("backgroundNoiseTextureOpacity"),
+ DEFAULT_SETTINGS.backgroundNoiseTextureOpacity,
+ "number",
+ ),
+ iconColor: parseQueryParam(searchParams.get("iconColor"), DEFAULT_SETTINGS.iconColor),
+ iconSize: parseQueryParam(searchParams.get("iconSize"), DEFAULT_SETTINGS.iconSize, "number"),
+ iconOffsetX: parseQueryParam(searchParams.get("iconOffsetX"), DEFAULT_SETTINGS.iconOffsetX, "number"),
+ iconOffsetY: parseQueryParam(searchParams.get("iconOffsetY"), DEFAULT_SETTINGS.iconOffsetY, "number"),
+ icon: parseQueryParam(searchParams.get("icon"), DEFAULT_SETTINGS.icon) as IconName,
+ fileName: parseQueryParam(searchParams.get("fileName"), DEFAULT_SETTINGS.fileName),
+ selectedPresetIndex: null,
+ };
+
+ // Get the icon name
+ const iconName = settings.icon || "Dots";
+
+ // Check if icon exists
+ const availableIcons = Object.keys(Icons);
+ if (!availableIcons.includes(iconName)) {
+ return NextResponse.json(
+ {
+ error: `Icon "${iconName}" not found`,
+ availableIcons: availableIcons.slice(0, 10),
+ totalIcons: availableIcons.length,
+ },
+ { status: 400 },
+ );
+ }
+
+ // Get size from query params
+ const size = parseQueryParam(searchParams.get("size"), 512, "number");
+
+ // Generate the SVG
+ const svgString = generateSVG(settings, size, iconName);
+
+ // Return SVG
+ return new NextResponse(svgString, {
+ headers: {
+ "Content-Type": "image/svg+xml",
+ "Cache-Control": "public, max-age=31536000, immutable",
+ },
+ });
+ } catch (error) {
+ console.error("Error generating icon:", error);
+ return NextResponse.json(
+ { error: "Failed to generate icon", details: error instanceof Error ? error.message : String(error) },
+ { status: 500 },
+ );
+ }
+}