# How to Integrate Evernote with V0

- Tool: V0
- Difficulty: Intermediate
- Time required: 35 minutes
- Last updated: April 2026

## TL;DR

To integrate Evernote with V0 by Vercel, generate a note browsing interface with V0, create a Next.js API route that calls the Evernote API to fetch notebooks, notes, and tags, store your Evernote developer token in Vercel environment variables, and deploy. Your app displays Evernote content through a secure server-side proxy without exposing your access token to the browser.

## Build Custom Evernote Note Browsers with V0 and Next.js

Evernote remains a widely used personal knowledge management tool, particularly among professionals who have built large archives of notes over the years. While Evernote's native app provides note access, many developers want to display Evernote content within custom apps — showing relevant notes in a CRM, surfacing tagged notes in a project dashboard, or building a focused reading interface for specific notebooks. The Evernote API enables all of these use cases through programmatic access to notebooks, notes, tags, and search.

The Evernote API uses the Apache Thrift binary protocol, which is more complex than typical REST APIs. The official evernote npm package abstracts this complexity into a JavaScript client that makes it practical to work with. The key objects in Evernote's data model are NoteStore (the primary API interface for notes and notebooks), notebooks (containers for notes), notes (with metadata and content stored separately), and tags. Notes have a two-phase fetch pattern: first you fetch a list of note metadata (guid, title, created, updated, tags), then you fetch the full note content (ENML format — a subset of XHTML) separately for notes you want to display.

V0's strength in generating custom table and card interfaces is well-matched to Evernote's note listing and browsing use cases. The note search interface (search by tag, notebook, or keyword) makes an excellent V0 candidate since it's primarily a data display problem. V0 handles the layout; the Evernote API handling stays in the Next.js API routes. Note that Evernote's developer API access requires applying for API keys and agreeing to their developer terms — the process typically takes 1-3 days for approval.

## Before you start

- An Evernote account — a free personal account works for development; a Premium or Professional account may be needed for API access to large archives
- An Evernote developer token — generated at dev.evernote.com/doc/articles/dev_tokens.php after creating a developer account (sandbox tokens are available immediately; production tokens require API key approval)
- The evernote npm package installed in your Next.js project — run npm install evernote in your project directory
- A V0 account at v0.dev to generate the note browsing interface
- A Vercel account to deploy the Next.js app and store environment variables

## Step-by-step guide

### 1. Generate the Note Browsing Interface with V0

Open V0 at v0.dev and describe the note browsing or display interface you want to build. Evernote interfaces work well as two or three column layouts: a narrow notebook sidebar, a note list panel, and an optional note detail panel. For simpler interfaces (just a note list), a table or card grid is sufficient. Be explicit in your V0 prompt about the data fields you want to show — Evernote note metadata includes guid (the unique identifier used in API calls), title, created (Unix timestamp in milliseconds), updated (Unix timestamp in milliseconds), and tagNames (array of tag strings). Ask V0 to generate components that accept these fields so the wiring is clean. V0 generates React components with Tailwind CSS and shadcn/ui that handle the layout and interactivity. Since Evernote's API uses the evernote npm package (not a simple REST call), all API work happens in Next.js API routes. The V0 component just fetches data from your API routes via fetch calls. After generating the component, push to GitHub using V0's Git panel and then move on to creating the API routes.

**Expected result:** A note browser renders in V0's preview with a notebook sidebar, note list with metadata, and note detail panel with drawer. The component fetches from the Next.js API routes you'll create.

### 2. Install the Evernote Package and Create the API Routes

Install the evernote npm package in your Next.js project, then create the API routes for notebooks, note listing, and note content. Run npm install evernote in your project directory before writing the route code. The Evernote npm package provides an Evernote.Client that handles the Thrift protocol abstraction. You initialize the client with your developer token and the sandbox flag (false for production, true for the Evernote sandbox). The NoteStore is the primary API object — get it via client.getNoteStore(), then use noteStore.listNotebooks() for notebooks, noteStore.findNotesMetadata() for note lists with search filters, and noteStore.getNoteContent() for note content. The findNotesMetadata method accepts a NoteFilter (for search/notebook filtering) and a NotesMetadataResultSpec (for specifying which metadata fields to return). Always specify resultSpec.includeTitle = true and resultSpec.includeUpdated = true to get the fields you need for display. Note that the evernote package returns content in ENML format (a subset of XHTML) — you can display this in a React component using dangerouslySetInnerHTML after stripping the ENML wrapper tags, but be aware of XSS risks if the content is from untrusted sources.

```
// app/api/evernote/notebooks/route.ts
import { NextResponse } from 'next/server';
import Evernote from 'evernote';

export async function GET() {
  const token = process.env.EVERNOTE_TOKEN;

  if (!token) {
    return NextResponse.json({ error: 'Evernote is not configured' }, { status: 500 });
  }

  const client = new Evernote.Client({
    token,
    sandbox: false, // true for dev.evernote.com sandbox
    china: false,
  });

  try {
    const noteStore = client.getNoteStore();
    const notebooks = await noteStore.listNotebooks();

    return NextResponse.json({
      notebooks: notebooks.map((nb) => ({
        guid: nb.guid,
        name: nb.name,
        updateSequenceNum: nb.updateSequenceNum,
        defaultNotebook: nb.defaultNotebook || false,
      })),
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Unknown error';
    console.error('Evernote notebooks error:', message);
    return NextResponse.json(
      { error: 'Failed to fetch notebooks', details: message },
      { status: 500 }
    );
  }
}

// app/api/evernote/notes/route.ts
// (separate file — shown here as a combined reference)
// import { NextRequest, NextResponse } from 'next/server';
// import Evernote from 'evernote';
//
// export async function GET(request: NextRequest) {
//   const { searchParams } = new URL(request.url);
//   const notebookGuid = searchParams.get('notebookGuid');
//   const tagName = searchParams.get('tag');
//   const words = searchParams.get('q');
//
//   const filter = new Evernote.NoteStore.NoteFilter({
//     notebookGuid: notebookGuid || undefined,
//     tagGuids: undefined,
//     words: tagName ? `tag:${tagName}` : words || undefined,
//   });
//
//   const resultSpec = new Evernote.NoteStore.NotesMetadataResultSpec({
//     includeTitle: true,
//     includeUpdated: true,
//     includeCreated: true,
//     includeTagGuids: true,
//     includeTagNames: true,
//   });
//
//   const noteStore = client.getNoteStore();
//   const result = await noteStore.findNotesMetadata(filter, 0, 50, resultSpec);
//   return NextResponse.json({ notes: result.notes, totalNotes: result.totalNotes });
// }
```

**Expected result:** GET /api/evernote/notebooks returns a list of your Evernote notebooks with guid and name. Create a separate route at app/api/evernote/notes/route.ts using the same pattern to return note metadata for a given notebook.

### 3. Create the Note Content Route and Connect the UI

Create an API route for fetching individual note content and wire all routes to the V0-generated component. The note content endpoint needs a dynamic route to accept the note GUID: create app/api/evernote/note/[guid]/route.ts. The noteStore.getNote() method returns full note data including the ENML content. ENML is an XML-based format — to display it in a React component, strip the <?xml> declaration and <en-note> wrapper tags and pass the inner HTML using dangerouslySetInnerHTML. For production apps with sensitive data, sanitize the HTML with a library like DOMPurify before rendering. Update your V0 component to fetch notebooks on mount, then notes for the selected notebook, then note content when a note is clicked. The loading states V0 generates (skeleton cards while fetching) improve perceived performance since Evernote API calls can take 1-2 seconds for large note archives. Consider implementing client-side caching with React's useState or a simple in-memory Map to avoid re-fetching notebooks and note metadata on every click — Evernote data doesn't change frequently enough to need real-time freshness for most use cases.

```
// app/api/evernote/note/[guid]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Evernote from 'evernote';

export async function GET(
  request: NextRequest,
  { params }: { params: { guid: string } }
) {
  const token = process.env.EVERNOTE_TOKEN;

  if (!token) {
    return NextResponse.json({ error: 'Evernote is not configured' }, { status: 500 });
  }

  const { guid } = params;

  if (!guid) {
    return NextResponse.json({ error: 'Note GUID is required' }, { status: 400 });
  }

  const client = new Evernote.Client({
    token,
    sandbox: false,
    china: false,
  });

  try {
    const noteStore = client.getNoteStore();

    // Fetch note with content
    const note = await noteStore.getNote(
      guid,
      true, // withContent
      false, // withResourcesData
      false, // withResourcesRecognition
      false  // withResourcesAlternateData
    );

    // Strip ENML wrapper to get displayable HTML
    let content = note.content || '';
    // Remove XML declaration
    content = content.replace(/<\?xml[^>]*>/, '').trim();
    // Replace en-note wrapper with a div
    content = content
      .replace(/<en-note[^>]*>/, '<div class="enml-content">')
      .replace(/<\/en-note>/, '</div>');

    return NextResponse.json({
      guid: note.guid,
      title: note.title,
      content,
      created: note.created,
      updated: note.updated,
      tagNames: note.tagNames || [],
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Unknown error';
    console.error('Evernote note content error:', message);
    return NextResponse.json(
      { error: 'Failed to fetch note content', details: message },
      { status: 500 }
    );
  }
}
```

**Expected result:** Clicking a note in the browser opens a drawer with the note's formatted HTML content, title, tags, and last updated date. The content renders from Evernote's ENML format as readable HTML.

### 4. Add the Environment Variable and Deploy to Vercel

Push your code to GitHub and configure the Evernote developer token in Vercel. Open the Vercel Dashboard, navigate to your project, and go to Settings → Environment Variables. Add EVERNOTE_TOKEN with your Evernote developer token — for production use, this is the token from your approved Evernote API application. For initial development and testing, use the sandbox token from dev.evernote.com/doc/articles/dev_tokens.php, but remember to update to a production token and change sandbox: false before the final deployment. The developer token is a long alphanumeric string — do not add the NEXT_PUBLIC_ prefix, as this is a server-side secret. Set the variable for Production, Preview, and Development environments. Important: if you're in the Evernote sandbox (testing environment), set a separate EVERNOTE_SANDBOX variable so you can conditionally switch between sandbox and production based on the deployment environment. After adding the variable, trigger a redeployment. Test the live deployment by visiting /api/evernote/notebooks — you should see your notebooks list. Then test the full note browsing flow through the UI. For production Evernote API applications, the approval process requires submitting your use case to Evernote — plan for 1-3 business days for approval before going live.

**Expected result:** The Vercel deployment builds successfully, the notebook list loads from the Evernote API, and notes can be browsed and read from the deployed app. The EVERNOTE_TOKEN is working correctly in the server environment.

## Best practices

- Always use sandbox: false in production and confirm it matches the type of token you're using — sandbox tokens are silently rejected by the production API
- Cache Evernote notebook and note metadata responses in your component state to avoid unnecessary API calls when users navigate between notebooks
- Sanitize ENML content with a library like DOMPurify before rendering with dangerouslySetInnerHTML, especially for notes that might contain external links or embedded content
- Fetch only note metadata in the list view and fetch full note content on demand (lazy loading) to avoid slow initial page loads when notebooks have many notes
- Use the resultSpec parameter in findNotesMetadata to request only the fields you display — avoiding unused fields reduces API response sizes for large notebooks
- Remember that Evernote timestamps are in Unix milliseconds — use new Date(timestamp) directly (not new Date(timestamp * 1000)) unlike Unix second timestamps from some other APIs
- For production apps, apply for official Evernote API access early in development — the approval process requires submitting your use case and can take several days

## Use cases

### Personal Note Archive Browser

A custom read-only interface for browsing Evernote notebooks and notes with a cleaner design than the default Evernote app. Users can navigate notebooks, view note titles and metadata, and open notes in a reading panel. Useful for personal productivity dashboards or custom knowledge bases built on top of an existing Evernote archive.

Prompt example:

```
Create a note archive browser with a left sidebar listing notebook names (from /api/evernote/notebooks) with note counts, a main panel with a table of notes showing title, created date, and tags as colored chips, and a right panel that shows note content when a note is selected (fetched from /api/evernote/notes/[guid]). Add a search bar above the notes table. Use a minimal, clean design with a sans-serif font and subtle borders.
```

### CRM Notes Panel

A side panel in a CRM or project management tool that shows Evernote notes tagged with a specific client name or project identifier. Sales reps or project managers can view their meeting notes and research from within the CRM without switching to Evernote.

Prompt example:

```
Build a notes panel component that takes a tagName prop and displays all Evernote notes with that tag (fetched from /api/evernote/notes?tag=CLIENT_NAME). Show each note as a card with its title, last updated date, and a preview of the first 200 characters. Include an 'Open in Evernote' link using the note's web URL. Style it as a compact side panel with a light gray background, suitable for embedding in a CRM sidebar.
```

### Tagged Resource Library

A curated resource library built from Evernote notes tagged with specific topic tags. Visitors can filter by tag and search note titles. Each note appears as a resource card with the title, tags, and a snippet of content.

Prompt example:

```
Design a resource library page that displays Evernote notes as resource cards (from /api/evernote/notes?notebook=NOTEBOOK_ID). Include a tag filter bar at the top, a search input, and a card grid where each card shows the note title, publication date, tag badges, and the first 150 characters of content. Add an external link button to open the note in Evernote web. Use a knowledge base style: white cards on a light gray background with clear typography.
```

## Troubleshooting

### API returns 'EDAMUserException: errorCode=AUTH_EXPIRED_TOKEN' or similar authentication error

Cause: The Evernote developer token is either invalid, expired, or a sandbox token being used against the production API (or vice versa). Developer tokens for the sandbox environment only work at sandbox.evernote.com, not at www.evernote.com.

Solution: Verify the token is correct at dev.evernote.com/doc/articles/dev_tokens.php. For production tokens, ensure you have approved API access from Evernote. Check that the sandbox parameter in Evernote.Client matches the token type — sandbox: true for dev.evernote.com tokens, sandbox: false for production tokens.

```
// Sandbox token configuration:
const client = new Evernote.Client({
  token: process.env.EVERNOTE_TOKEN,
  sandbox: process.env.NODE_ENV === 'development', // Use sandbox in dev
  china: false,
});
```

### Note content displays as raw XML or ENML tags instead of readable text

Cause: The ENML content is being rendered without stripping the XML declaration and en-note wrapper, or dangerouslySetInnerHTML is not being used in the React component to render the HTML.

Solution: Strip the XML declaration and replace the en-note wrapper with a div before passing content to the component, then use dangerouslySetInnerHTML to render it: <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />. The code in Step 3 demonstrates the correct stripping approach.

```
// In your React component:
<div 
  className="note-content prose max-w-none"
  dangerouslySetInnerHTML={{ __html: note.content }} 
/>
```

### evernote package TypeScript errors: 'Module not found' or type errors

Cause: The evernote npm package has limited TypeScript support and may require type declaration adjustments. V0 generates TypeScript by default but the evernote package types may not be perfectly aligned with current TypeScript strict mode settings.

Solution: Add @types/evernote if available, or add a type declaration file. If imports fail, try importing as: import Evernote from 'evernote' or as a CommonJS require. Check that the package is installed: run npm list evernote in your project directory to verify installation.

```
// If default import fails, try:
const Evernote = require('evernote');
// Or add to your tsconfig.json:
// "esModuleInterop": true
```

### EVERNOTE_TOKEN is undefined in the Vercel deployment

Cause: The environment variable was not configured in Vercel before deployment, was set after the latest deployment without triggering a redeploy, or was accidentally prefixed with NEXT_PUBLIC_.

Solution: Go to Vercel Dashboard → Settings → Environment Variables, verify EVERNOTE_TOKEN exists without any prefix, then redeploy from the Deployments tab. Environment variable changes require a new deployment to take effect.

## Frequently asked questions

### Do I need to pay for an Evernote account to use the API?

A free Evernote account provides access to the developer API for testing through the sandbox environment. For production API access with real user data, you need to apply for API keys from Evernote. Evernote's API access approval may be limited to paid plan holders depending on their current developer policy — check dev.evernote.com for the current requirements as policies have changed several times over the years.

### Can I create or edit Evernote notes from my V0 app?

Yes — the NoteStore provides createNote() and updateNote() methods. Creating a note requires building a Note object with title and ENML-formatted content. ENML content must be valid XML starting with the proper DOCTYPE declaration. For simple text notes, wrap the content in the ENML format: '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note>Your content here</en-note>'.

### What is the difference between the Evernote sandbox and production environments?

The Evernote sandbox (dev.evernote.com) is a completely separate environment from production (evernote.com) with separate accounts, notes, and tokens. Your real Evernote notes are not accessible in the sandbox — you must create test data there separately. Sandbox tokens have the 'S=' prefix convention to distinguish them from production tokens. Always test in sandbox first, then switch to production tokens and sandbox: false before the final deployment.

### How do I search Evernote notes from my app?

Use the Evernote Search Grammar in the NoteFilter.words parameter. Common search syntax: 'tag:projectname' for tag searches, 'notebook:"My Notebook"' for notebook-filtered searches, 'intitle:meeting' for title searches, and free-text terms for full-text search. Pass these as the words property in a new Evernote.NoteStore.NoteFilter object to findNotesMetadata.

### Why does V0 not generate Evernote-connected code directly?

V0 is a UI generation tool focused on the visual layer of React applications. It generates components and basic fetch patterns, but specific API integrations like Evernote's Thrift-based API require custom server-side code using the evernote npm package. The pattern is: V0 generates the UI, you add the Next.js API routes connecting to Evernote.

### What does ENML mean and how do I display it properly?

ENML stands for Evernote Markup Language — it's a restricted subset of XHTML used to store Evernote note content. It looks like HTML but includes Evernote-specific elements like <en-todo> (checkboxes) and <en-media> (attachments). To display ENML as readable HTML, strip the XML declaration and replace <en-note> with a <div>, then use dangerouslySetInnerHTML in React. For rich rendering including checkboxes and media, you need additional CSS and possibly custom element handling.

---

Source: https://www.rapidevelopers.com/v0-integrations/evernote
© RapidDev — https://www.rapidevelopers.com/v0-integrations/evernote
