This Spring Boot app authenticates with QuickBooks via OAuth 2.0 (Java SDK), fetches Customers/Items via the Accounting API, and manages Custom Fields using the QuickBooks GraphQL API.
- Custom Fields Operations (UI demo)
- Create a custom field via GraphQL
- List existing custom fields via GraphQL
- Disable a custom field definition via GraphQL
- Activate a custom field definition via GraphQL
- Create invoice with a custom field value via REST
- Deep link to view the created invoice in QBO
- Create customer with a custom field value via REST
- Deep link to view the created customer in QBO
- Direct deep link to the Custom Fields settings page in QBO
- Hybrid Integration
- REST (Accounting API) for Customers, Items, and Invoices
- GraphQL for Custom Fields
- OAuth 2.0 (Java SDK)
- Discovery-based environment handling (sandbox/production)
- Secure redirect with configurable URI
- Diagnostics
- Configurable logging for REST and GraphQL requests
- Inline duplicate-name handling for customer creation (scoped error under the form with input focus)
Availability: Custom Fields GraphQL APIs are currently available in production environments only.
- Spring Boot Web and Thymeleaf
- QuickBooks Java SDKs:
ipp-v3-java-devkit,oauth2-platform-api,ipp-v3-java-data(6.7.0) - JAXB for Java 17 compatibility:
javax.xml.bind:jaxb-api,org.glassfish.jaxb:jaxb-runtime - Jackson Databind
- Spring Session Core
- Spring Boot Starter Test (test scope)
- Java 17 or higher
- Gradle 7.0 or higher
- QuickBooks Developer account and a QuickBooks Online company
- ngrok (for local development)
-
Note: Custom Fields GraphQL APIs are currently available in PRODUCTION only (not supported in Sandbox).
-
Ensure you connect to a production company when testing these scopes.
-
com.intuit.quickbooks.accounting— access accounting data (customers, items, invoices) -
app-foundations.custom-field-definitions— manage custom field definitions (GraphQL) -
app-foundations.custom-field-definitions.read— read custom field definitions (GraphQL)
- Clone the repository
git clone <repository-url>
cd SampleApp-CustomFields-Java- Start ngrok
ngrok http 8080- Configure your QuickBooks app
- Visit the Intuit Developer Portal (
https://developer.intuit.com/app/developer/myapps) - Create an app (or use an existing one)
- Enable the scopes above
- Add your redirect URI (e.g.,
https://your-ngrok-url.ngrok-free.app/callback)
- Configure application settings
Option A: Use environment variables
export QB_CLIENT_ID=your_client_id
export QB_CLIENT_SECRET=your_client_secret
export QB_REDIRECT_URI=https://your-ngrok-url.ngrok-free.app/callback
export QB_ENVIRONMENT=production # or sandbox
export QB_GRAPHQL_URL=https://qb.api.intuit.com/graphql
export QB_BASE_URL=https://quickbooks.api.intuit.com
export QB_MINOR_VERSION=75
export QB_SCOPES="com.intuit.quickbooks.accounting,indirect-tax.tax-calculation.quickbooks"Option B: Use application.yml via the provided template
Location: src/main/resources/application.yml.template (reference only)
Copy this template to src/main/resources/application.yml and then edit values:
server:
port: 8080
forward-headers-strategy: framework
servlet:
session:
cookie:
secure: true
same-site: none
spring:
application:
name: quickbooks-sdk-demo
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
quickbooks:
# INSTRUCTIONS: Copy this block into application.yml and replace with your credentials
client-id: ${QB_CLIENT_ID:your_quickbooks_client_id_here}
client-secret: ${QB_CLIENT_SECRET:your_quickbooks_client_secret_here}
environment: ${QB_ENVIRONMENT:production} # Custom Fields GraphQL is production-only
redirect-uri: ${QB_REDIRECT_URI:https://your-ngrok-url.ngrok-free.app/callback}
base-url: ${QB_BASE_URL:https://quickbooks.api.intuit.com}
graphql-url: ${QB_GRAPHQL_URL:https://qb.api.intuit.com/graphql}
minor-version: ${QB_MINOR_VERSION:75}
deep-link-template: ${QB_DEEP_LINK_TEMPLATE:https://app.qbo.intuit.com/app/invoice?txnId=%s&companyId=%s}
customer-deep-link-template: ${QB_CUSTOMER_DEEP_LINK_TEMPLATE:https://app.qbo.intuit.com/app/customerdetail?nameId=%s&companyId=%s}
custom-fields-deep-link-template: ${QB_CUSTOM_FIELDS_DEEP_LINK_TEMPLATE:https://app.qbo.intuit.com/app/customfields?companyId=%s}
scopes:
- com.intuit.quickbooks.accounting
- app-foundations.custom-field-definitions
- app-foundations.custom-field-definitions.read
logging:
level:
com.quickbooks.demo: DEBUG
org.springframework.graphql: DEBUG
org.springframework.web.client.RestTemplate: DEBUGNotes:
- The live
application.ymlis intentionally gitignored to protect secrets. Only the.templatefile is committed as a reference. - Do not edit the
.templatefile for your environment; create/maintain your ownapplication.ymllocally.
- Run the application
./gradlew bootRun- Visit
http://localhost:8080 - Click "Connect to QuickBooks" to authenticate (
/qbo-login) - After auth, the home page loads Customers and Items from your QBO company
- Enter a custom field name and click "Create Custom Field" (GraphQL)
- (Optional) Use "Disable Custom Field" to deactivate an existing custom field definition
- (Optional) Use "Activate Custom Field" to reactivate a previously disabled custom field definition
- (Optional) Create a Customer with a custom field value (REST)
- If a duplicate name is submitted, an inline error appears right under the form and the name field is focused for quick correction
- Follow the deep links to view the customer or open the Custom Fields settings page directly
- Select a customer and item, optionally provide a custom field value, and click "Create Invoice" (REST)
- Follow the deep link to open the invoice in QBO
Step 1 — OAuth connect
Step 2 — Create Custom Field
Step 3 — Disable Custom Field (optional)
Step 4 — Create Invoice
Step 5 — View Custom Fields in QBO
Step 6 — Customer Custom Field
/— Home page/qbo-login— Initiate OAuth flow/callback— OAuth callback/create_custom_field— Create a custom field (GraphQL)/disable_custom_field— Disable a custom field definition (GraphQL)/activate_custom_field— Activate a custom field definition (GraphQL)/create_invoice— Create invoice with optional custom field (REST)/create_customer— Create customer with optional custom field (REST)
-
OAuth (SDK)
- Build auth URL → SDK discovery (OpenID configuration) to determine endpoints
- Exchange code →
POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer - Refresh token →
POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer
-
Accounting (REST)
- Get Customers →
POST {BASE}/v3/company/{realmId}/query?minorversion={mv}with bodySelect * from Customer ... - Get Items →
POST {BASE}/v3/company/{realmId}/query?minorversion={mv}with bodySelect * from Item ... - Create Customer →
POST {BASE}/v3/company/{realmId}/customer?minorversion={mv} - Create Invoice →
POST {BASE}/v3/company/{realmId}/invoice?minorversion={mv}&include=enhancedAllCustomFields- Headers:
Authorization: Bearer <token>,Content-Type: application/json,Accept: application/json
- Headers:
- Get Customers →
-
Custom Fields (GraphQL)
- Create Custom Field →
POST https://qb.api.intuit.com/graphql(mutation instatic/graphql/custom_fields.graphql) - List Custom Fields →
POST https://qb.api.intuit.com/graphql(query instatic/graphql/custom_fields.graphql)
- Create Custom Field →
Notes:
{BASE}is typicallyhttps://quickbooks.api.intuit.com(prod) or sandbox host if configured.minorversionis configurable inapplication.yml.
SampleApp-CustomFields-Java/
├── src/main/java/com/quickbooks/demo/
│ ├── Application.java
│ ├── config/
│ │ └── QuickBooksConfig.java
│ ├── controller/
│ │ └── QuickBooksController.java
│ └── service/
│ ├── CustomFieldsService.java
│ ├── QuickBooksApiService.java
│ └── QuickBooksOAuthService.java
├── src/main/resources/
│ ├── application.yml
│ ├── static/graphql/
│ │ ├── custom_fields.graphql
│ │ └── custom_field_variables.json
│ ├── static/css/
│ └── templates/
└── README.md
- Insufficient scope (GraphQL error: access denied / AUTHORIZATION_DENIED)
- Ensure GraphQL scopes are configured and granted during auth
- If you change scopes, re-authenticate to obtain a new token
- Invalid client or redirect URI
- Verify Client ID/Secret
- Ensure
redirect-uriexactly matches the Developer Portal configuration
- Environment mismatch
- Ensure
environmentmatches the company you connect to (sandbox vs production)
- Custom Field Limits
- You may reach business limits for the number of custom fields; use an existing field or contact your admin
This project is licensed under the Apache License 2.0 — see LICENSE.
For support, visit the Intuit Developer Community (https://help.developer.intuit.com/s/) or open an issue in this repository.





