Home/Devlog

Building a Figma Variables Manager for my plugin

A deep dive into creating a robust Figma Variables Manager, from basic implementation to a production-ready system with proper error handling, type safety...

Nov 24, 2024 · 4 min read

I'm working on a DS Plugin where wanted to create and manage figma variable easily when you start a new project. I able to create a demo of the plugin you can check out here.

After spending countless hours building and refining a Figma Variables Manager, I wanted to share my journey and the key lessons I learned along the way. This isn't just about the code—it's about the thought process, problem-solving, and challenges I encountered throughout. While Figma's documentation offers plenty of examples on how to use specific APIs, creating a better, more robust system proved to be a much more complex task.

How it started for demo

For demo I just hardcoded everthing like:

const collection = figma.variables.createVariableCollection("Design Tokens");
const modeId = collection.modes[0]?.modeId || collection.addMode("Default");
    
const variable = figma.variables.createVariable(name, collection as any, 'COLOR');

Then I realized it would be hard to manage later, so I started working on a more organized structure for managing variables and collections. It began with a seemingly simple task: just retrieve a variable if it exists, or create it if it doesn’t. Simple, right? Here’s an example of how I started working on searching and managing variables:

function getVariable(name: string) {
  return figma.variables.getLocalVariables()
    .find(v => v.name === name);
}

Oh, how naive I was! This quickly fell apart when I encountered:

  • Variables with the same name in different collections
  • Different variable types (colors vs numbers)
  • Missing error handling

So, started adding handling

After my code failed silently in production (a humbling experience), I learned the importance of proper error handling:

function getVariable(name: string) {
  try {
    return figma.variables.getLocalVariables()
      .find(v => v.name === name);
  } catch (error) {
    console.error('Failed to get variable:', error);
    throw error; 
  }
}

Then added collection check

function getVariable(name: string, collectionId: string) {
   try {
  return figma.variables.getLocalVariables()
    .find(v => 
      v.name === name && 
      v.variableCollectionId === collectionId
    ); 
  } catch (error) {
    console.error('Failed to get variable:', error);
    throw error; 
  }
}

Then type safety

The "undefined is not a function" errors taught me to respect TypeScript's type system:

type VariableResolvedDataType = 'COLOR' | 'FLOAT' | 'STRING' | 'BOOLEAN';

function getVariable(
  name: string, 
  collection: VariableCollection,
  type: VariableResolvedDataType
) {
   // Now type check prevent errors
}

Final touch. Combining Get and Create

Then I realized I needed to combine getting and creating variables into a single operation. Instead for created two seprate fuction combined together. This led to the final implementation:

type VariableResolvedDataType = 'COLOR' | 'FLOAT' | 'STRING' | 'BOOLEAN';

static getOrCreateVariable(
  name: string,
  collection: VariableCollection,
  type: VariableResolvedDataType = 'COLOR' //default type
): Variable {
  try {
    const existingVariable = figma.variables.getLocalVariables(type)
      .find(v => v.name === name && v.variableCollectionId === collection.id);

    if (existingVariable) {
      return existingVariable;
    }

    return figma.variables.createVariable(name, collection as any, type);
  } catch (error) {
    console.error(`Error in getOrCreateVariable for ${name}:`, error);
    throw error;
  }
}

What next

So, I kept working on it and created a FigmaVariableManager, where you can find all the functions needed to handle variables and collections. This makes it easier to use when building a plugin that requires managing these elements. Link shared below.

Key Lessons Learned

  • Combining Get and Create Operations: Streamlining these operations helps prevent race conditions, ensuring smoother performance and data integrity.

  • Enhancing Reliability: A well-designed approach makes the code more dependable, especially in production environments where consistency is key.

  • Prioritize Error Handling: Silent failures can be more harmful than loud crashes. Providing detailed error messages not only improves debugging efficiency but also enhances the overall user experience.

  • The Power of TypeScript: TypeScript goes beyond catching errors—it serves as self-documenting code and eliminates entire classes of runtime issues, making development more efficient.

  • Understanding Variable Systems: In Figma, Variables are part of a larger ecosystem, including collections, modes, and types. Coding should reflect and respect these intricate relationships for better scalability and usability.

Practical Tips

Here are some tips from my experience:

// Don't just check the name
if (variable.name === name) // Bad

// Check both name and collection
if (variable.name === name && 
    variable.variableCollectionId === collection.id) // Good
// Provide defaults
type: VariableResolvedDataType = 'COLOR'
// Include context in error messages
console.error(`Error in getOrCreateVariable for ${name}:`, error);

I shared complete script on Gist. You can find all variable and collection related fuctions there. Get it from here. Let me know if you have any feedback.


WIP mock of Plugin I'm working on

You can signup here

stay hungry, stay foolish

-Steve Jobs

©realvjyvijay verma