2) Backend — add logo to every slide (masters + content)

Update your server/api/ppt/build-template.ts:

Read assets.logoDataUrl, assets.logoPlacement, assets.logoScale.

Add logo into:

The Title/Master slides (defineSlideMaster objects).

Every generated slide (so it truly appears across the deck).

Replace the file with this version:

import { Router } from "express";
import PPTXGenJS from "pptxgenjs";

const router = Router();

type BuildBody = {
  size: "16x9" | "4x3";
  brand: {
    name?: string;
    fonts: { heading: string; body: string };
    accents: string[];
    hasBrandLogo?: boolean;
  };
  assets: {
    logoDataUrl: string | null;
    logoPlacement: "none" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
    logoScale: number; // 0.6 - 2
  };
  selections: {
    layouts: string[];
    infographics: string[];
  };
};

router.post("/", async (req, res) => {
  try {
    const { size, brand, assets, selections } = req.body as BuildBody;

    const pptx = new PPTXGenJS();
    pptx.layout = size === "4x3" ? "LAYOUT_4x3" : "LAYOUT_16x9";

    const slideW = pptx.width;  // inches
    const slideH = pptx.height; // inches

    // Helpers -------------
    function addLogoToSlide(slide: PPTXGenJS.Slide) {
      if (!assets.logoDataUrl || assets.logoPlacement === "none") return;

      const base = 1.2; // default inches (a bit smaller than covers)
      const w = base * (assets.logoScale || 1);
      const h = w; // assume square-ish
      const m = 0.25;

      let x = m, y = m;
      switch (assets.logoPlacement) {
        case "top-left":      x = m;             y = m;             break;
        case "top-right":     x = slideW - w - m; y = m;             break;
        case "bottom-left":   x = m;              y = slideH - h - m; break;
        case "bottom-right":  x = slideW - w - m; y = slideH - h - m; break;
      }

      slide.addImage({ data: assets.logoDataUrl, x, y, w, h });
    }

    // Masters -------------
    // Title Master (with logo if provided)
    pptx.defineSlideMaster({
      title: "Master Title",
      background: { color: rgb(brand.accents[0], "363636") },
      objects: [
        { text: { text: brand.name || "Your Company", options: { x: 1, y: 2, fontSize: 36, color: "FFFFFF", fontFace: brand.fonts.heading, bold: true } } },
        ...(assets.logoDataUrl && assets.logoPlacement !== "none"
          ? [logoObject(assets, slideW, slideH)]
          : []),
      ],
    });

    // Section Divider Master (with logo)
    pptx.defineSlideMaster({
      title: "Section Divider",
      background: { color: rgb(brand.accents[1], "4F81BD") },
      objects: [
        { text: { text: "Section Title", options: { x: 1, y: 2, fontSize: 28, color: "FFFFFF", fontFace: brand.fonts.heading, bold: true } } },
        ...(assets.logoDataUrl && assets.logoPlacement !== "none"
          ? [logoObject(assets, slideW, slideH)]
          : []),
      ],
    });

    // Slides -------------
    // Title slide using master
    const titleSlide = pptx.addSlide({ masterName: "Master Title" });
    // (Optional extra text box for tagline could be added here)

    // Example section divider
    const divider = pptx.addSlide({ masterName: "Section Divider" });
    divider.addText("Overview", { x: 1, y: 3.2, fontSize: 20, color: "FFFFFF", fontFace: brand.fonts.body });

    // Body Layouts
    selections.layouts.forEach((layoutId) => {
      const slide = pptx.addSlide();
      slide.addText(layoutId, { x: 1, y: 0.6, fontSize: 20, fontFace: brand.fonts.heading });
      slide.addText("Content area", { x: 1, y: 1.4, fontSize: 14, fontFace: brand.fonts.body, color: rgb(brand.accents[2], "333333") });
      addLogoToSlide(slide);
    });

    // Infographics
    selections.infographics.forEach((inf) => {
      const slide = pptx.addSlide();
      slide.addText(`Infographic: ${inf}`, { x: 1, y: 0.6, fontSize: 20, fontFace: brand.fonts.heading });

      const sections = parseInt(inf.match(/\d+/)?.[0] || "2", 10);
      const totalW = slideW - 1.0; // margins
      const boxW = (totalW - (sections - 1) * 0.1) / sections;
      for (let i = 0; i < sections; i++) {
        slide.addShape(pptx.ShapeType.rect, {
          x: 0.5 + i * (boxW + 0.1),
          y: 1.6,
          w: boxW,
          h: 2.2,
          fill: { color: rgb(brand.accents[i % brand.accents.length], "6B7280") },
        });
      }
      addLogoToSlide(slide);
    });

    // Output -------------
    const buf = await pptx.write("nodebuffer");
    res.setHeader("Content-Disposition", 'attachment; filename="ibrandbiz-template.pptx"');
    res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
    res.send(buf);
  } catch (e: any) {
    console.error("PPT build error", e);
    res.status(500).json({ error: "Failed to build PPTX" });
  }
});

export default router;

// ---- helpers ----
function rgb(maybeHex?: string, fallbackHex?: string) {
  const h = (maybeHex || fallbackHex || "000000").replace("#", "");
  return h.length === 6 ? h.toUpperCase() : "000000";
}

function logoObject(
  assets: BuildBody["assets"],
  slideW: number,
  slideH: number
): PPTXGenJS.SlideMasterObject {
  const base = 1.2;
  const w = base * (assets.logoScale || 1);
  const h = w;
  const m = 0.25;

  let x = m, y = m;
  switch (assets.logoPlacement) {
    case "top-left":      x = m;             y = m;             break;
    case "top-right":     x = slideW - w - m; y = m;             break;
    case "bottom-left":   x = m;              y = slideH - h - m; break;
    case "bottom-right":  x = slideW - w - m; y = slideH - h - m; break;
    case "none": default: x = -999; y = -999; // won't render
  }

  return {
    image: { data: assets.logoDataUrl!, x, y, w, h },
  };
}


Notes:

Masters: Title + Section Divider include the logo via defineSlideMaster.objects.

All other slides: addLogoToSlide(slide) drops the logo so it’s visible deck-wide.

Placement options: none / top-left / top-right / bottom-left / bottom-right.

Scale: 0.6–2.0 via your slider.

3) Frontend — call stays the same

You already updated generateTemplate() to POST buildConfig(). No further changes needed; the backend now respects assets.logoDataUrl / placement / scale.

Done

User can use Brand Kit logo or upload a client logo.

Choose placement + size.

Generated PPTX shows the logo on masters and every slide.

No other features altered. 🚀