All skills
figma avatar

/figma-use-figjam

@a5e7e04 official
by figmafigma/mcp-server-guide2k stars
194

This skill helps agents use Figma's use_figma MCP tool in the FigJam context. Can be used alongside figma-use which has foundational context for using the use_figma tool.

Use this Skill: https://skilld.dev/gh/figma/mcp-server-guide/figma-use-figjam

This session only. Nothing lands on disk.

referencesedit-text.md

≈3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Text Operations

Part of the figma-use-figjam skill. Editing existing text content, styles, and font segments.

Critical: Load Fonts First

Follow the canonical text-edit recipe — load font → await → mutate → return affected IDs. Skipping the load throws Cannot write to node with unloaded font "<family> <style>". Inter is preloaded in most environments; every other family (and every Inter style you haven't already loaded) still needs an explicit loadFontAsync.

// Load a single font
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })

// Load a font used by a single-font text node
await figma.loadFontAsync(textNode.fontName)

// Load all fonts in a text node (handles mixed fonts via styled segments)
const segments = textNode.getStyledTextSegments(['fontName'])
await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

Complete Working Example

const nodeId = '123:456'
const node = await figma.getNodeByIdAsync(nodeId)

if (node && node.type === 'TEXT') {
  // Load all fonts used in this text node (handles mixed fonts via styled segments)
  const segments = node.getStyledTextSegments(['fontName'])
  await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

  console.log('Before:', node.characters)
  node.characters = 'New text content'
  console.log('After:', node.characters)

  // Verify with image
  const img = await node.exportAsync({
    format: 'PNG',
    constraint: { type: node.width > node.height ? 'WIDTH' : 'HEIGHT', value: 128 },
  })
  figma.io.write(`${node.name.replace(/[^a-z0-9]/gi, '_')}_result.png`, img)
}
figma.closePlugin()

Basic Properties

Follows the canonical text-edit recipe — load every (family, style) you're about to assign (including the NEW font when changing fontName) before any text mutation.

// Required for any font, not just Inter
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })

// Text content
textNode.characters = 'Hello, World!'

// Font
textNode.fontName = { family: 'Inter', style: 'Bold' }

// Size
textNode.fontSize = 16

// Color (via fills) — use Charcoal (#1E1E1E) as default
textNode.fills = [{ type: 'SOLID', color: { r: 0x1e / 255, g: 0x1e / 255, b: 0x1e / 255 } }]

Text Color in FigJam

CRITICAL: When editing text in FigJam board content (templates, brainstorms, retros, or any generated content), always use Charcoal (#1E1E1E) as the text color unless the user has specifically requested different colors. Use hex/255 notation for exact palette matching. For non-text node colors and the canonical palette across all FigJam node types, see figjam-colors.

// Charcoal — default for all FigJam text
textNode.fills = [{ type: 'SOLID', color: { r: 0x1e / 255, g: 0x1e / 255, b: 0x1e / 255 } }]

Do not use grey (#757575, #B3B3B3) or light grey (#D9D9D9) for body text, headers, or descriptions — these make content look unfinished and hard to read.

Text Alignment

// Horizontal alignment
textNode.textAlignHorizontal = 'LEFT' // LEFT, CENTER, RIGHT, JUSTIFIED

// Vertical alignment
textNode.textAlignVertical = 'TOP' // TOP, CENTER, BOTTOM

Line Height and Spacing

// Line height
textNode.lineHeight = { value: 150, unit: 'PERCENT' }
textNode.lineHeight = { value: 24, unit: 'PIXELS' }
textNode.lineHeight = { unit: 'AUTO' }

// Letter spacing
textNode.letterSpacing = { value: 0, unit: 'PERCENT' }
textNode.letterSpacing = { value: 1, unit: 'PIXELS' }

// Paragraph spacing
textNode.paragraphSpacing = 16

// Paragraph indentation
textNode.paragraphIndent = 24

Text Decoration

Underlines and strikethroughs support styling (wavy, dotted), offset, and color.

textNode.textDecoration = 'UNDERLINE' // NONE, UNDERLINE, STRIKETHROUGH
textNode.textDecorationStyle = 'WAVY' // SOLID, DOTTED, WAVY
textNode.textDecorationOffset = { unit: 'PIXELS', value: 2 }
textNode.textDecorationColor = { value: { type: 'SOLID', color: { r: 1, g: 0, b: 0 } } } // Custom color
textNode.textDecorationColor = { value: 'AUTO' } // Inherit from text color

Text Case

// Case transformation
textNode.textCase = 'ORIGINAL' // ORIGINAL, UPPER, LOWER, TITLE, SMALL_CAPS, SMALL_CAPS_FORCED

Text Sizing Behavior

// Auto-resize mode
textNode.textAutoResize = 'WIDTH_AND_HEIGHT' // Auto-size both
textNode.textAutoResize = 'HEIGHT' // Fixed width, auto height
textNode.textAutoResize = 'NONE' // Fixed size
// Truncation pattern (preferred over deprecated textAutoResize = 'TRUNCATE')
textNode.textAutoResize = 'NONE' // Keep fixed bounds
textNode.textTruncation = 'ENDING' // Truncate overflow with ellipsis
textNode.maxLines = 2 // Optional: cap visible lines

Styled Ranges (Mixed Styles)

For text with different styles in different parts:

await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })

textNode.characters = 'Hello World'

// Make "Hello" bold (characters 0-5)
textNode.setRangeFontName(0, 5, { family: 'Inter', style: 'Bold' })

// Make "World" red (characters 6-11)
textNode.setRangeFills(6, 11, [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }])

// Change size of "World"
textNode.setRangeFontSize(6, 11, 24)

// Get properties for a range
const fontAtStart = textNode.getRangeFontName(0, 1)
const sizeAtEnd = textNode.getRangeFontSize(10, 11)

Available Range Methods

  • setRangeFontName(start, end, fontName)
  • setRangeFontSize(start, end, size)
  • setRangeFills(start, end, fills)
  • setRangeTextDecoration(start, end, decoration)
  • setRangeTextCase(start, end, textCase)
  • setRangeLetterSpacing(start, end, spacing)
  • setRangeLineHeight(start, end, lineHeight)
  • setRangeHyperlink(start, end, hyperlink)
  • setRangeListOptions(start, end, listOptions)
  • setRangeIndentation(start, end, indentation)

Hyperlinks

// Set hyperlink
textNode.setRangeHyperlink(0, 5, { type: 'URL', value: 'https://figma.com' })

// Node link
textNode.setRangeHyperlink(0, 5, { type: 'NODE', value: '123:456' })

// Remove hyperlink
textNode.setRangeHyperlink(0, 5, null)

Lists

// Bulleted list
textNode.setRangeListOptions(0, textNode.characters.length, { type: 'UNORDERED' })

// Numbered list
textNode.setRangeListOptions(0, textNode.characters.length, { type: 'ORDERED' })

// Remove list
textNode.setRangeListOptions(0, textNode.characters.length, { type: 'NONE' })

Getting Text Segments

// Get all styled segments
const segments = textNode.getStyledTextSegments(['fontName', 'fontSize', 'fills', 'textDecoration'])

for (const segment of segments) {
  console.log(`"${segment.characters}" - ${segment.fontName.family} ${segment.fontSize}px`)
}

Inserting and Deleting Characters

// Load all fonts (handles mixed fonts via styled segments)
const segments = textNode.getStyledTextSegments(['fontName'])
await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

// Insert text at a position
textNode.insertCharacters(0, 'Hello ') // Insert at start
textNode.insertCharacters(textNode.characters.length, '!') // Insert at end
textNode.insertCharacters(6, 'beautiful ') // Insert in middle

// Delete characters (start, end)
textNode.deleteCharacters(0, 6) // Delete first 6 characters
textNode.deleteCharacters(5, 10) // Delete characters 5-9

figma.closePlugin()

Splitting Text into Multiple Nodes

To split a text node into separate nodes (one per line/paragraph):

const nodeId = '123:456'
const node = await figma.getNodeByIdAsync(nodeId)

if (node && node.type === 'TEXT') {
  // Load all fonts (handles mixed fonts via styled segments)
  const segments = node.getStyledTextSegments(['fontName'])
  await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

  const lines = node.characters.split(/\r?\n/)
  const parent = node.parent
  const index = parent.children.indexOf(node)

  // Calculate line height for positioning
  const lineHeight =
    typeof node.lineHeight === 'object' && node.lineHeight.unit === 'PIXELS'
      ? node.lineHeight.value
      : node.fontSize * 1.2

  const createdNodes = []
  for (const line of lines) {
    if (!line.trim()) continue

    // Clone preserves ALL properties (lineHeight, letterSpacing, etc.)
    const newNode = node.clone()
    newNode.characters = line.trim()

    // Position vertically using lineHeight (for non-auto-layout parents)
    if (parent.layoutMode === 'NONE' || !parent.layoutMode) {
      newNode.y = node.y + createdNodes.length * lineHeight
    }

    parent.insertChild(index + createdNodes.length, newNode)
    createdNodes.push(newNode)
  }

  node.remove()
  console.log(`Split into ${createdNodes.length} nodes`)
}

figma.closePlugin()

Key points:

  1. Use clone() to preserve all text properties (lineHeight, letterSpacing, textCase, etc.)
  2. Position using lineHeight directly - use PIXELS value if set, otherwise fontSize * 1.2
  3. For verification exports, export the parent frame - don't use temporary grouping

Find and Replace Across Page

To search and replace text content across many nodes:

;(async () => {
  const searchText = 'Sign Up'
  const replaceText = 'Register'
  const caseSensitive = false

  const textNodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] })
  console.log(`Searching ${textNodes.length} text nodes for "${searchText}"...`)

  const regex = new RegExp(
    searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
    caseSensitive ? 'g' : 'gi',
  )

  let totalReplacements = 0
  let nodesModified = 0

  for (const node of textNodes) {
    if (!regex.test(node.characters)) continue
    regex.lastIndex = 0

    // Load all fonts used in this node (handles mixed fonts via styled segments)
    const segments = node.getStyledTextSegments(['fontName'])
    await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

    const matches = node.characters.match(regex)?.length || 0
    console.log(`  ${node.name}: "${node.characters}" → ${matches} match(es)`)

    node.characters = node.characters.replace(regex, replaceText)
    totalReplacements += matches
    nodesModified++
  }

  console.log(`Replaced ${totalReplacements} occurrence(s) across ${nodesModified} node(s)`)
  figma.closePlugin()
})()

Scoped Find and Replace (Within a Frame)

const frame = await figma.getNodeByIdAsync('123:456')
if (frame && 'findAllWithCriteria' in frame) {
  const textNodes = frame.findAllWithCriteria({ types: ['TEXT'] })
  // ... same replacement logic as above
}

Preserving Styled Ranges

When text has mixed styling (bold + regular), replacing characters wholesale resets all styling to the first character's style. To preserve ranges, use deleteCharacters + insertCharacters:

async function replacePreservingStyles(node, search, replace) {
  // Load all fonts (handles mixed fonts via styled segments)
  const segments = node.getStyledTextSegments(['fontName'])
  await Promise.all(segments.map((s) => figma.loadFontAsync(s.fontName)))

  let idx = node.characters.indexOf(search)
  while (idx !== -1) {
    node.deleteCharacters(idx, idx + search.length)
    node.insertCharacters(idx, replace)
    idx = node.characters.indexOf(search, idx + replace.length)
  }
}

OpenType Features

// Get current features for a range
const features = textNode.getRangeOpenTypeFeatures(0, 1)

// Inspect node-level OpenType features
const nodeFeatures = textNode.openTypeFeatures
if (nodeFeatures !== figma.mixed) {
  console.log('LIGA:', nodeFeatures.LIGA, 'CALT:', nodeFeatures.CALT)
}

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides documentation and code patterns for using Figma's official MCP tools in FigJam files. It is safe and contains no malicious code or instructions.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at a5e7e04. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 20 hours ago.

Activeupdated last month
disable-model-invocation
false
  • MCP
  • figma
  • figjam
  • plugin-api
  • board-creation
  • collaborative-design
  • javascript

README badge

README badge for figma/mcp-server-guide/figma-use-figjam

Instructs the use_figma MCP tool for FigJam-specific operations: creating boards, sections, stickies, connectors, tables, and code blocks. Covers inspection via get_figjam, text mutations, image uploads, and positioning rules that differ from Figma Design files.

Generated from the current SKILL.md.

Do I call `figma.createPage()` in FigJam files?
No. FigJam files (figma.com/board/...) do not support `createPage()` and will throw a TypeError. FigJam has a single implicit page; use sections instead to organize content.
How do I inspect a FigJam file to get node IDs?
`get_figjam` is the inspection tool for FigJam files and returns the full node tree as XML with all IDs. Always call it upfront before writing `use_figma` code that references existing nodes, since `console.log` output from `use_figma` is not returned to the agent.
Can I use `get_metadata` on FigJam files?
No. `get_metadata` is design-mode only and will fail immediately on FigJam files with an 'unsupported for FigJam files' error.
How do I add images to a FigJam board?
`upload_assets` is the only supported way to add images to FigJam. Do not use `figma.createImage()` or `figma.createImageAsync()` from inside `use_figma`.
What is the canonical recipe for editing text in FigJam?
Load the font, await it, then mutate the text node, then return affected IDs. FigJam text nodes use varying default fonts (sticky/shape use Inter Medium, connector has no default), so always load the actual font from `node.text.fontName` rather than hardcoding a family.

Generated from the current SKILL.md. These answers refresh after source changes.