Documentation Index Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
Use this file to discover all available pages before exploring further.
Building Your First App
This guide walks you through building a complete LaunchMyStore app from scratch. By the end, you’ll have a working embedded app that can read products and display them in the admin.
What We’re Building
A simple “Product Insights” app that:
Authenticates via OAuth
Embeds in the LaunchMyStore admin
Fetches and displays product data
Uses App Bridge for native UI elements
Prerequisites
Node.js 18 or higher
A LaunchMyStore developer account
Basic knowledge of JavaScript/React
Part 1: Project Setup
Create the Project
mkdir product-insights-app
cd product-insights-app
npm init -y
Install Dependencies
npm install express dotenv @launchmystore/app-bridge
npm install -D nodemon
Project Structure
product-insights-app/
├── .env
├── package.json
├── server.js
├── public/
│ └── app.html
└── lib/
└── session.js
Create a .env file:
LMS_CLIENT_ID=your_client_id
LMS_CLIENT_SECRET=your_client_secret
LMS_APP_URL=https://your-tunnel-url.ngrok.io
LMS_SCOPES=read_products,read_orders
PORT=3000
Part 3: Build the Server
server.js
require ( 'dotenv' ). config ();
const express = require ( 'express' );
const path = require ( 'path' );
const { saveSession , getSession } = require ( './lib/session' );
const app = express ();
app . use ( express . json ());
app . use ( express . static ( 'public' ));
const {
LMS_CLIENT_ID ,
LMS_CLIENT_SECRET ,
LMS_APP_URL ,
LMS_SCOPES ,
PORT = 3000
} = process . env ;
// OAuth: Start installation
app . get ( '/auth/install' , ( req , res ) => {
const { shop } = req . query ;
if ( ! shop ) {
return res . status ( 400 ). send ( 'Missing shop parameter' );
}
const state = Buffer . from ( JSON . stringify ({ shop , nonce: Date . now () })). toString ( 'base64' );
const authUrl = new URL ( 'https://api.launchmystore.io/oauth/authorize' );
authUrl . searchParams . set ( 'client_id' , LMS_CLIENT_ID );
authUrl . searchParams . set ( 'scope' , LMS_SCOPES );
authUrl . searchParams . set ( 'redirect_uri' , ` ${ LMS_APP_URL } /auth/callback` );
authUrl . searchParams . set ( 'state' , state );
res . redirect ( authUrl . toString ());
});
// OAuth: Handle callback
app . get ( '/auth/callback' , async ( req , res ) => {
const { code , state } = req . query ;
try {
const { shop } = JSON . parse ( Buffer . from ( state , 'base64' ). toString ());
// Exchange code for tokens
const tokenResponse = await fetch ( 'https://api.launchmystore.io/oauth/token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
client_id: LMS_CLIENT_ID ,
client_secret: LMS_CLIENT_SECRET ,
code ,
grant_type: 'authorization_code' ,
redirect_uri: ` ${ LMS_APP_URL } /auth/callback`
})
});
const tokens = await tokenResponse . json ();
if ( tokens . error ) {
throw new Error ( tokens . error_description || tokens . error );
}
// Save session
await saveSession ( shop , {
accessToken: tokens . access_token ,
refreshToken: tokens . refresh_token ,
expiresAt: Date . now () + ( tokens . expires_in * 1000 ),
scope: tokens . scope
});
// Redirect to app
const host = Buffer . from ( ` ${ shop } /admin` ). toString ( 'base64' );
res . redirect ( `/app?shop= ${ shop } &host= ${ host } ` );
} catch ( error ) {
console . error ( 'OAuth error:' , error );
res . status ( 500 ). send ( 'Authentication failed' );
}
});
// App entry point
app . get ( '/app' , async ( req , res ) => {
const { shop } = req . query ;
const session = await getSession ( shop );
if ( ! session ) {
return res . redirect ( `/auth/install?shop= ${ shop } ` );
}
res . sendFile ( path . join ( __dirname , 'public' , 'app.html' ));
});
// API proxy to fetch products
app . get ( '/api/products' , async ( req , res ) => {
const { shop } = req . query ;
const session = await getSession ( shop );
if ( ! session ) {
return res . status ( 401 ). json ({ error: 'Not authenticated' });
}
try {
const response = await fetch ( 'https://api.launchmystore.io/api/v1/products' , {
headers: {
'Authorization' : `Bearer ${ session . accessToken } ` ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
res . json ( data );
} catch ( error ) {
console . error ( 'API error:' , error );
res . status ( 500 ). json ({ error: 'Failed to fetch products' });
}
});
app . listen ( PORT , () => {
console . log ( `App running on http://localhost: ${ PORT } ` );
});
lib/session.js
Simple in-memory session storage (use a database in production):
const sessions = new Map ();
async function saveSession ( shop , data ) {
sessions . set ( shop , data );
}
async function getSession ( shop ) {
return sessions . get ( shop );
}
async function deleteSession ( shop ) {
sessions . delete ( shop );
}
module . exports = { saveSession , getSession , deleteSession };
Part 4: Build the Frontend
public/app.html
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Product Insights </ title >
< script src = "https://cdn.launchmystore.io/app-bridge.js" ></ script >
< style >
* { box-sizing : border-box ; margin : 0 ; padding : 0 ; }
body { font-family : -apple-system , BlinkMacSystemFont, 'Segoe UI' , Roboto, sans-serif ; padding : 20 px ; background : #f6f6f7 ; }
.card { background : white ; border-radius : 8 px ; padding : 20 px ; margin-bottom : 16 px ; box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , 0.1 ); }
.header { display : flex ; justify-content : space-between ; align-items : center ; margin-bottom : 20 px ; }
h1 { font-size : 20 px ; font-weight : 600 ; }
.btn { background : #008060 ; color : white ; border : none ; padding : 10 px 16 px ; border-radius : 6 px ; cursor : pointer ; font-size : 14 px ; }
.btn:hover { background : #006e52 ; }
.product-grid { display : grid ; grid-template-columns : repeat ( auto-fill , minmax ( 250 px , 1 fr )); gap : 16 px ; }
.product { display : flex ; gap : 12 px ; align-items : center ; }
.product img { width : 50 px ; height : 50 px ; object-fit : cover ; border-radius : 6 px ; }
.product-info h3 { font-size : 14 px ; font-weight : 500 ; }
.product-info p { font-size : 12 px ; color : #6b7280 ; }
.loading { text-align : center ; padding : 40 px ; color : #6b7280 ; }
</ style >
</ head >
< body >
< div class = "header" >
< h1 > Product Insights </ h1 >
< button class = "btn" onclick = " refreshProducts ()" > Refresh </ button >
</ div >
< div class = "card" >
< div id = "products" class = "loading" > Loading products... </ div >
</ div >
< script >
// Initialize App Bridge
const urlParams = new URLSearchParams ( window . location . search );
const host = urlParams . get ( 'host' );
const shop = urlParams . get ( 'shop' );
const app = window . LMSAppBridge . createApp ({
apiKey: 'YOUR_CLIENT_ID' , // Replace with your Client ID
host: host
});
// Show loading toast
app . dispatch ({
type: 'LOADING_START'
});
// Fetch and display products
async function loadProducts () {
try {
const response = await fetch ( `/api/products?shop= ${ shop } ` );
const data = await response . json ();
app . dispatch ({ type: 'LOADING_STOP' });
if ( data . error ) {
throw new Error ( data . error );
}
renderProducts ( data . products || []);
} catch ( error ) {
app . dispatch ({
type: 'TOAST' ,
payload: { message: 'Failed to load products' , isError: true }
});
document . getElementById ( 'products' ). innerHTML = 'Failed to load products' ;
}
}
function renderProducts ( products ) {
const container = document . getElementById ( 'products' );
if ( products . length === 0 ) {
container . innerHTML = '<p>No products found</p>' ;
return ;
}
container . className = 'product-grid' ;
container . innerHTML = products . map ( product => `
<div class="product">
<img src=" ${ product . image ?. src || 'https://via.placeholder.com/50' } " alt=" ${ product . title } ">
<div class="product-info">
<h3> ${ product . title } </h3>
<p> ${ product . variants ?.[ 0 ]?. price || 'N/A' } </p>
</div>
</div>
` ). join ( '' );
}
function refreshProducts () {
app . dispatch ({
type: 'TOAST' ,
payload: { message: 'Refreshing products...' }
});
loadProducts ();
}
// Load products on page load
loadProducts ();
</ script >
</ body >
</ html >
Part 5: Run and Test
Start the Server
Expose with ngrok
Update Your App Settings
Go to your app in the developer dashboard
Update the App URL to your ngrok URL
Add the callback URL: https://your-ngrok-url/auth/callback
Install the App
Navigate to:
https://your-ngrok-url/auth/install?shop=your-store-slug
Next Steps
Add Extensions Extend storefronts and checkout
Add Functions Custom shipping and payment logic
Webhooks React to store events in real-time