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.
Integration method
Evernote integrates with V0-generated Next.js apps through server-side API routes that proxy calls to the Evernote Thrift-over-HTTPS API. Your Evernote developer token is stored as a server-only Vercel environment variable. The React components V0 generates fetch note and notebook data from your Next.js routes, which in turn call Evernote's API using the evernote npm package. This keeps your access token secure and avoids CORS issues since Evernote's API does not support direct browser calls.
Prerequisites
- 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
Generate the Note Browsing Interface with V0
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.
Create a notebook and note browser with a responsive two-column layout. Left column: a scrollable list of notebook names with note counts (from /api/evernote/notebooks), with the active notebook highlighted. Right column: a list of notes for the selected notebook (from /api/evernote/notes?notebookGuid=ID) showing note title, last updated date formatted as 'Apr 15, 2026', and up to 3 tag badges per note. Clicking a note title opens a drawer on the right with the full note (from /api/evernote/note/GUID). Include a top search bar. Use a productivity app aesthetic with a white and light blue color scheme.
Paste this in V0 chat
Pro tip: Evernote timestamps are in Unix milliseconds (not seconds), so pass them directly to new Date(timestamp) in JavaScript — unlike some APIs that use seconds, Evernote timestamps are already milliseconds-precision.
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.
Install the Evernote Package and Create the API Routes
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.
1// app/api/evernote/notebooks/route.ts2import { NextResponse } from 'next/server';3import Evernote from 'evernote';45export async function GET() {6 const token = process.env.EVERNOTE_TOKEN;78 if (!token) {9 return NextResponse.json({ error: 'Evernote is not configured' }, { status: 500 });10 }1112 const client = new Evernote.Client({13 token,14 sandbox: false, // true for dev.evernote.com sandbox15 china: false,16 });1718 try {19 const noteStore = client.getNoteStore();20 const notebooks = await noteStore.listNotebooks();2122 return NextResponse.json({23 notebooks: notebooks.map((nb) => ({24 guid: nb.guid,25 name: nb.name,26 updateSequenceNum: nb.updateSequenceNum,27 defaultNotebook: nb.defaultNotebook || false,28 })),29 });30 } catch (error) {31 const message = error instanceof Error ? error.message : 'Unknown error';32 console.error('Evernote notebooks error:', message);33 return NextResponse.json(34 { error: 'Failed to fetch notebooks', details: message },35 { status: 500 }36 );37 }38}3940// app/api/evernote/notes/route.ts41// (separate file — shown here as a combined reference)42// import { NextRequest, NextResponse } from 'next/server';43// import Evernote from 'evernote';44//45// export async function GET(request: NextRequest) {46// const { searchParams } = new URL(request.url);47// const notebookGuid = searchParams.get('notebookGuid');48// const tagName = searchParams.get('tag');49// const words = searchParams.get('q');50//51// const filter = new Evernote.NoteStore.NoteFilter({52// notebookGuid: notebookGuid || undefined,53// tagGuids: undefined,54// words: tagName ? `tag:${tagName}` : words || undefined,55// });56//57// const resultSpec = new Evernote.NoteStore.NotesMetadataResultSpec({58// includeTitle: true,59// includeUpdated: true,60// includeCreated: true,61// includeTagGuids: true,62// includeTagNames: true,63// });64//65// const noteStore = client.getNoteStore();66// const result = await noteStore.findNotesMetadata(filter, 0, 50, resultSpec);67// return NextResponse.json({ notes: result.notes, totalNotes: result.totalNotes });68// }Pro tip: The Evernote sandbox environment (dev.evernote.com) uses a separate token from the production environment. Always test with the sandbox first and set sandbox: true in the Evernote.Client constructor during development.
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.
Create the Note Content Route and Connect the UI
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.
Update the note browser so that clicking a notebook in the sidebar calls /api/evernote/notes?notebookGuid=GUID to load that notebook's notes. Clicking a note opens a side drawer and calls /api/evernote/note/GUID to fetch the note content. Display note content as HTML in the drawer (it will be sanitized ENML/HTML). Cache the notebook list and note metadata in component state so switching notebooks doesn't trigger redundant API calls. Show skeleton loaders during each fetch transition.
Paste this in V0 chat
1// app/api/evernote/note/[guid]/route.ts2import { NextRequest, NextResponse } from 'next/server';3import Evernote from 'evernote';45export async function GET(6 request: NextRequest,7 { params }: { params: { guid: string } }8) {9 const token = process.env.EVERNOTE_TOKEN;1011 if (!token) {12 return NextResponse.json({ error: 'Evernote is not configured' }, { status: 500 });13 }1415 const { guid } = params;1617 if (!guid) {18 return NextResponse.json({ error: 'Note GUID is required' }, { status: 400 });19 }2021 const client = new Evernote.Client({22 token,23 sandbox: false,24 china: false,25 });2627 try {28 const noteStore = client.getNoteStore();2930 // Fetch note with content31 const note = await noteStore.getNote(32 guid,33 true, // withContent34 false, // withResourcesData35 false, // withResourcesRecognition36 false // withResourcesAlternateData37 );3839 // Strip ENML wrapper to get displayable HTML40 let content = note.content || '';41 // Remove XML declaration42 content = content.replace(/<\?xml[^>]*>/, '').trim();43 // Replace en-note wrapper with a div44 content = content45 .replace(/<en-note[^>]*>/, '<div class="enml-content">')46 .replace(/<\/en-note>/, '</div>');4748 return NextResponse.json({49 guid: note.guid,50 title: note.title,51 content,52 created: note.created,53 updated: note.updated,54 tagNames: note.tagNames || [],55 });56 } catch (error) {57 const message = error instanceof Error ? error.message : 'Unknown error';58 console.error('Evernote note content error:', message);59 return NextResponse.json(60 { error: 'Failed to fetch note content', details: message },61 { status: 500 }62 );63 }64}Pro tip: ENML content may include Evernote-specific elements like <en-todo> (checkboxes) and <en-media> (attachments) that won't render visually without CSS. Add basic CSS rules for these elements or strip them before displaying.
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.
Add the Environment Variable and Deploy to Vercel
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.
Pro tip: Evernote sandbox and production environments use different tokens AND different API hostnames. The evernote npm package handles the hostname automatically based on the sandbox: true/false setting, but you must use the matching token for each environment.
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.
Common 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.
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.
Copy this prompt to try it in V0
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.
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.
Copy this prompt to try it in V0
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.
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.
Copy this prompt to try it in V0
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.
1// Sandbox token configuration:2const client = new Evernote.Client({3 token: process.env.EVERNOTE_TOKEN,4 sandbox: process.env.NODE_ENV === 'development', // Use sandbox in dev5 china: false,6});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.
1// In your React component:2<div 3 className="note-content prose max-w-none"4 dangerouslySetInnerHTML={{ __html: note.content }} 5/>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.
1// If default import fails, try:2const Evernote = require('evernote');3// Or add to your tsconfig.json:4// "esModuleInterop": trueEVERNOTE_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.
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
Alternatives
Use Notion instead of Evernote for new projects — Notion's public API is more modern, better documented, and actively developed compared to Evernote's older Thrift-based API.
Choose Google Docs integration if your notes or documents are already in Google Workspace, since the Google Docs API offers richer document editing capabilities and is more broadly adopted.
Use Confluence if your note-taking needs are team-oriented rather than personal — Confluence is built for collaborative team documentation with version control and space organization.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation