npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@useinsider/guido

v3.6.0

Published

Guido is a Vue + TypeScript wrapper for Email Plugin. Easily embed the email editor in your Vue applications.

Readme

@useinsider/guido

Coverus

Guido is a Vue 2 + TypeScript wrapper for the Stripo Email Editor plugin. Easily embed the professional email editor in your Vue applications with a clean, type-safe configuration.

Installation

npm install @useinsider/guido

Prerequisites

Your project needs:

  • Vue 2.7+
  • Pinia (state management)

Add these to your bundler config to optimize dependencies:

Webpack (webpack.config.js or vue.config.js):

shared: {
  vue: { singleton: true },
  pinia: { singleton: true },
},

Vite (vite.config.js):

resolve: {
  dedupe: ['vue', 'pinia'],
},

Quick Start

<template>
  <Guido
    ref="guidoRef"
    :config="config"
    @ready="onReady"
    @dynamic-content:open="onDynamicContentOpen"
    @save:complete="onSaveComplete"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { Guido } from '@useinsider/guido';
import type { GuidoConfigInput } from '@useinsider/guido';

const guidoRef = ref<InstanceType<typeof Guido> | null>(null);

const config = ref<GuidoConfigInput>({
  identity: {
    templateId: 'template-123',
    userId: 'user-456',
  },
  partner: {
    name: 'your-partner-name',
  },
  template: {
    html: '<p>Initial content</p>',
    css: '',
  },
});

const onReady = () => console.log('Editor ready');
const onDynamicContentOpen = () => console.log('Dynamic content requested');
const onSaveComplete = (template) => console.log('Saved:', template);
</script>

Configuration

Guido uses a single, validated config prop organized by domain:

import type { GuidoConfigInput } from '@useinsider/guido';

const config: GuidoConfigInput = {
  // Required: Identity
  identity: {
    templateId: string,      // Required - Template identifier
    userId: string,          // Required - User identifier
    variationId?: string,    // Optional - A/B test variation
  },

  // Required: Partner
  partner: {
    name: string,            // Required - Partner name
    productType?: number,    // Optional - Default: 60 (Email)
    messageType?: number,    // Optional - Default: 1 (Promotional)
    username?: string,       // Optional - Default: 'Guido User'
  },

  // Optional: Template content
  template?: {
    html?: string,
    css?: string,
    preselectedDynamicContent?: DynamicContent[],
    selectedUnsubscribePages?: number[],
    forceRecreate?: boolean,       // Default: false - Force recreate template in Stripo storage
    migration?: {
      // Legacy block configs keyed by block ID. Consumed once by the migrator
      // when upgrading templates authored with v1 block formats. See the
      // "Template Migration" section below.
      recommendationConfigs?: Record<string, LegacyRecommendationConfig>,
    },
  },

  // Optional: Editor settings
  editor?: {
    locale?: string,              // Default: 'en'
    translationsPath?: string,    // Default: 'window.trans.en'
    migrationDate?: number,       // Stripo migration timestamp
    emailHeader?: {
      senderName?: string,
      subject?: string,
    },
    savedModulesFolderName?: string,    // Default: 'savedModules' - folder name for user-saved modules
    defaultModulesFolderName?: string,  // Default: 'defaultModules' - folder name for default/prebuilt modules
  },

  // Optional: UI settings
  ui?: {
    showHeader?: boolean,         // Default: true
    backButtonLabel?: string,
  },

  // Optional: Feature toggles
  features?: {
    dynamicContent?: boolean,     // Default: true
    saveAsTemplate?: boolean,     // Default: true
    versionHistory?: boolean,     // Default: true
    testMessage?: boolean,        // Default: true
    displayConditions?: boolean,  // Default: true
    unsubscribe?: boolean,        // Default: true
    modulesDisabled?: boolean,    // Default: false - Disable modules panel
    liquidSyntax?: boolean,       // Default: false - Enable Liquid template syntax
    autosave?: boolean,           // Default: false - Show the Auto Save toggle in the header. See wiki/AUTOSAVE.md.
  },

  // Optional: Callbacks
  callbacks?: {
    externalValidation?: (data: SavedTemplateDetails) => Promise<boolean>,  // Return false to cancel save
  },

  // Optional: Block configuration
  blocks?: {
    excludeDefaults?: DefaultBlockType[],
    includeCustoms?: CustomBlockType[],
  },

  // Optional: HTML compiler
  compiler?: {
    customRules?: CompilerRule[],
    ignoreDefaultRules?: boolean,  // Default: false
  },
};

Events

| Event | Payload | Description | |-------|---------|-------------| | ready | - | Editor is loaded and ready | | dynamic-content:open | DynamicContent \| null | User wants to insert dynamic content | | back | - | Back button clicked | | save:start | - | Save process started | | save:complete | SavedTemplateDetails | Save completed successfully (includes metadata) | | on-change | - | Template was modified | | test-email:click | - | Test email button clicked | | onboarding-finished | - | Onboarding popup dismissed |

save:complete Payload

The save:complete event emits a SavedTemplateDetails object containing the compiled template data along with editor metadata:

interface SavedTemplateDetails {
  dynamicContentList: DynamicContent[];
  compiledHtml: string;
  rawHtml: string;
  css: string;
  ampHtml: string;
  ampErrors: string[];
  modules: number[];
  recommendation: {
    campaignUrls: Record<string, string>;
    configs: Record<string, string>;
  };
  unsubscribe: {
    status: boolean;
    config: number[];
  };
  metadata: Metadata;
  silent: boolean;  // true when triggered by autosave, false when user clicked Save
}

interface Metadata {
  emailId: string;
  partnerName?: string;
  productType?: number;
  userId: string;
  username?: string;
  savedModulesFolderName?: string;
  defaultModulesFolderName?: string;
}

⚠️ Important: The metadata object in the save:complete payload must be passed directly into your templateConfig / stripoConfig object while saving. This ensures the email-service receives the correct version of sync modules features


Exposed Methods

const guidoRef = ref<InstanceType<typeof Guido> | null>(null);

// Insert dynamic content
guidoRef.value?.dynamicContent.insert({
  text: 'Display Name',
  value: 'variable_name',
  fallback: 'Default Value',
});

// Close dynamic content modal
guidoRef.value?.dynamicContent.close();

// Silent save (no UI feedback)
guidoRef.value?.saveSilent();

Block Configuration

Default Blocks (can be excluded)

type DefaultBlockType =
  | 'amp-accordion'
  | 'amp-carousel'
  | 'amp-form-controls'
  | 'banner-block'
  | 'button-block'
  | 'html-block'
  | 'image-block'
  | 'menu-block'
  | 'social-block'
  | 'spacer-block'
  | 'text-block'
  | 'timer-block'
  | 'video-block';

Custom Blocks (opt-in)

type CustomBlockType =
  | 'dynamic-content'
  | 'checkbox-block'
  | 'radio-button-block'
  | 'recommendation-block'
  | 'unsubscribe-block'
  | 'coupon-block'
  | 'items-block';

Examples

// Exclude specific default blocks
const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'partner' },
  blocks: {
    excludeDefaults: ['timer-block', 'video-block'],
  },
};

// Include custom blocks
const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'partner' },
  blocks: {
    includeCustoms: ['recommendation-block', 'coupon-block', 'dynamic-content'],
  },
};

Module Folder Configuration

Customize the Stripo module folder names for saved and default modules. These values are passed to Stripo metadata and the dynamic folder paths are constructed in the Stripo plugin panel configuration.

const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'acme' },
  editor: {
    // Folder name for user-saved modules
    savedModulesFolderName: 'savedModules',

    // Folder name for default/prebuilt modules
    defaultModulesFolderName: 'defaultModules',
  },
};

Default Values

| Config Option | Default Value | |--------------|---------------| | savedModulesFolderName | 'savedModules' | | defaultModulesFolderName | 'defaultModules' |

Note: The actual folder paths (e.g., guido_acme_savedModules) are configured in the Stripo plugin panel using variable substitution like ${savedModulesFolderName}.


Autosave

Guido ships an opt-in autosave that saves on a 3-minute interval and when the user leaves the tab. Enable it with the features.autosave feature flag — this shows an "Auto Save" toggle in the editor header; the end user switches autosave on per session.

const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'partner' },
  features: {
    autosave: true,   // Default: false — shows the Auto Save toggle in the header
  },
};
  • Default is false — integrations see no change unless they opt in.
  • Autosave reuses the same save pipeline as the Save button, so your existing @save:complete handler receives autosave output identically to a manual save. No new events or callbacks.
  • Toggle state is session-only (Pinia) — resets to OFF on reload.

For a deep dive on triggers, guards, and limitations, see wiki/AUTOSAVE.md.


Template Migration

When a template was authored with v1 block formats (notably the legacy recommendation block), Guido needs additional context that cannot be recovered from the saved HTML alone — things like filter rules, recommendation strategies, currency settings, locale, and pinned product IDs.

Pass that context once via template.migration when loading the template. The migrator consumes it during initial render to upgrade legacy blocks to the current format. After migration, the field has no effect on editor behavior — you can keep passing it (it's idempotent on already-migrated templates) or omit it.

When to provide this: Only when loading templates created before the v2 recommendation block was rolled out. New templates do not need it.

recommendationConfigs

A dictionary keyed by the legacy block's element ID. Each entry preserves the v1 block's runtime config so the migrator can hydrate the upgraded block.

const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'acme' },
  template: {
    html: legacyHtml,
    css: legacyCss,
    migration: {
      recommendationConfigs: {
        // Key = legacy block element id (also present as `id` in the entry)
        'recommendation-1700000000000': {
          id: 1700000000000,
          // Filter-driven block — productIds is empty, filters/strategy drive selection
          productIds: [],
          filters: [{ field: 'category', op: 'eq', value: 'shoes' }],
          strategy: 'newArrivals',
          shuffleProducts: false,
          sendProductRequestFlag: true,

          // Display & locale
          currency: 'EUR',
          currencySettings: { decimal: ',', thousand: '.', alignment: 'left' },
          language: 'nl_NL',
          decimalCount: 2,

          // Layout
          cardsInRow: 2,
          orientation: 'vertical',
          textTrimming: true,
          unresponsive: false,
          mobileLeftPadding: 0,
          mobileRightPadding: 0,

          // Snapshot of products as they were rendered in the legacy email
          recommendedProducts: [/* ...legacy product objects... */],
        },
      },
    },
  },
};

All fields on a LegacyRecommendationConfig entry are optional — pass whatever your storage layer has for that block. Unknown keys are preserved and forwarded to the migrator unchanged, so partner-specific extensions continue to round-trip.

Field reference

| Field | Type | Purpose | |-------|------|---------| | id | number | Block ID (matches the dictionary key and legacy HTML element id) | | productIds | unknown[] | Pinned product IDs — empty when filter-driven | | filters | unknown[] | Filter rules driving product selection | | strategy | string | Recommendation strategy key (e.g. 'newArrivals') | | shuffleProducts | boolean | Whether to randomize product order | | sendProductRequestFlag | boolean | Whether the block requested live products at send time | | currency | string | Currency code (e.g. 'EUR') | | currencySettings | unknown | Separators, alignment, decimals | | language | string | Locale (e.g. 'nl_NL') | | decimalCount | string \| number | Decimal places for price display | | cardsInRow | number | Product cards per row | | orientation | 'vertical' \| 'horizontal' | Layout orientation | | size | string \| number | Size variant marker (legacy) | | verticalResponsiveness | boolean | Vertical responsiveness flag (legacy size=1 variants) | | blockType | string | Block type marker used by some legacy variants | | textTrimming | boolean | Whether long text is trimmed | | unresponsive | boolean | Disable responsive scaling | | mobileLeftPadding / mobileRightPadding | number | Mobile-only horizontal padding | | recommendedProducts | unknown[] | Snapshot of products rendered by the legacy block |

Note on data shape: The schema uses looseObject because v1 partner data shapes vary across deployments. Some entries carry verticalResponsiveness, others carry blockType / orientation / size. Pass whatever you have — the migrator will use what it recognizes and preserve the rest.


HTML Compiler Rules

Add custom rules to transform HTML during export:

const config: GuidoConfigInput = {
  identity: { templateId: 'tpl-123', userId: 'user-456' },
  partner: { name: 'partner' },
  compiler: {
    customRules: [
      // Replace rule
      {
        id: 'replace-domain',
        type: 'replace',
        search: 'old-domain.com',
        replacement: 'new-domain.com',
        priority: 10,
      },
      // Regex rule
      {
        id: 'remove-comments',
        type: 'regex',
        pattern: '<!--.*?-->',
        replacement: '',
        flags: 'g',
        priority: 20,
      },
      // Custom processor
      {
        id: 'add-tracking',
        type: 'custom',
        processor: (html) => html.replace('</body>', '<img src="track.gif"/></body>'),
        priority: 30,
      },
    ],
    ignoreDefaultRules: false, // Set true to skip built-in rules
  },
};

TypeScript Exports

// Component
import { Guido } from '@useinsider/guido';

// Types
import type {
  GuidoConfig,
  GuidoConfigInput,
  IdentityConfig,
  PartnerConfig,
  TemplateConfig,
  TemplateMigrationConfig,
  LegacyRecommendationConfig,
  EditorConfig,
  UIConfig,
  FeaturesConfig,
  BlocksConfig,
  CompilerConfig,
  DynamicContent,
  DefaultBlockType,
  CustomBlockType,
} from '@useinsider/guido';

// Utilities
import {
  validateConfig,
  parseConfig,
  MessageType,
  ProductType,
} from '@useinsider/guido';

// Styles
import '@useinsider/guido/style';

Constants

import { MessageType, ProductType } from '@useinsider/guido';

MessageType.PROMOTIONAL    // 1
MessageType.TRANSACTIONAL  // 2

ProductType.EMAIL          // 60
ProductType.ARCHITECT      // 49
ProductType.UNSUBSCRIBE_PAGES // 97

Dynamic Content Modal

When the dynamic-content:open event fires, show your custom modal and call the insert method:

<template>
  <Guido ref="guidoRef" :config="config" @dynamic-content:open="showModal = true" />

  <!-- Your modal must have id="guido-dynamic-content-modal" -->
  <YourModal v-if="showModal" id="guido-dynamic-content-modal" @select="insertContent" @close="closeModal" />
</template>

<script setup>
const showModal = ref(false);

const insertContent = (content) => {
  guidoRef.value?.dynamicContent.insert({
    text: content.label,
    value: content.value,
    fallback: content.fallback || '',
  });
  showModal.value = false;
};

const closeModal = () => {
  guidoRef.value?.dynamicContent.close();
  showModal.value = false;
};
</script>

Development

# Install
bun install

# Start dev server
bun start

# Build
bun run build

# Lint & type-check
bun run lint

Environment Variables

VITE_STRIPO_PLUGIN_ID=your_plugin_id
VITE_STRIPO_SECRET_KEY=your_secret_key
VITE_STRIPO_ROLE=your_role

Local Testing

# Build the package
bun run build

# Create tarball
npm pack

# Install in your project
cd ../your-project
npm install ../guido/useinsider-guido-1.0.0.tgz

License

ISC License