Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ export type {
BaseResponse,
EngineParameters,
GetBySearchIdParameters,
GoogleSearchParameters,
GoogleSearchResponse,
KnowledgeGraph,
LocationsApiParameters,
OrganicResult,
RelatedQuestion,
SearchInformation,
SearchMetadata,
SearchParameters,
} from "./src/types.ts";
export {
getAccount,
Expand Down
133 changes: 133 additions & 0 deletions src/engines/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** Parameters for the Google Search engine. */
export interface GoogleSearchParameters {
engine: "google";
q: string;
api_key?: string;
timeout?: number;

// Geographic Location
location?: string;
uule?: string;
lat?: number;
lon?: number;
radius?: number;

// Localization
google_domain?: string;
gl?: string;
hl?: string;
cr?: string;
lr?: string;

// Search Type
tbm?: "isch" | "lcl" | "vid" | "nws" | "shop" | "pts";

// Pagination
start?: number;
num?: number;

// Advanced Filters
tbs?: string;
safe?: "active" | "off";
nfpr?: 0 | 1;
filter?: 0 | 1;

// Advanced Google Parameters
ludocid?: string;
lsig?: string;
kgmid?: string;
si?: string;

// SerpApi Parameters
device?: "desktop" | "tablet" | "mobile";
no_cache?: boolean;
async?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should extract out SerpApi Parameters and have the other engines' types extend it since nearly every other engine has the same SerpApi-specific params:https://serpapi.com/search-api#api-parameters-serpapi-parameters

What do you think @zyc9012

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library used to have types, but we removed them (#15) because all the parameters are unstable. We can add new engines/parameters from time to time, which makes it hard to sync with SDKs. I'm not sure if we want to bring types back.

// deno-lint-ignore no-explicit-any
[key: string]: any;
}

/** Metadata returned with every SerpApi response. */
export interface SearchMetadata {
id: string;
status: string;
json_endpoint: string;
created_at: string;
processed_at: string;
google_url: string;
raw_html_file: string;
total_time_taken: number;
}

/** Echoed search parameters in the response. */
export interface SearchParameters {
engine: string;
q: string;
google_domain?: string;
hl?: string;
gl?: string;
device?: string;
location_requested?: string;
location_used?: string;
[key: string]: unknown;
}

/** Search result info. */
export interface SearchInformation {
query_displayed: string;
total_results: number;
time_taken_displayed: number;
organic_results_state: string;
page_number?: number;
}

/** A single organic search result. */
export interface OrganicResult {
position: number;
title: string;
link: string;
redirect_link?: string;
displayed_link: string;
snippet: string;
snippet_highlighted_words?: string[];
date?: string;
sitelinks?: {
inline?: { title: string; link: string }[];
expanded?: { title: string; link: string; snippet: string }[];
};
rich_snippet?: Record<string, unknown>;
source?: string;
}

/** Knowledge graph result. */
export interface KnowledgeGraph {
title?: string;
type?: string;
description?: string;
source?: { name: string; link: string };
[key: string]: unknown;
}

/** "People also ask" question. */
export interface RelatedQuestion {
question: string;
snippet?: string;
title?: string;
link?: string;
}

/** Google Search JSON response. */
export interface GoogleSearchResponse {
search_metadata: SearchMetadata;
search_parameters: SearchParameters;
search_information?: SearchInformation;
organic_results?: OrganicResult[];
knowledge_graph?: KnowledgeGraph;
related_questions?: RelatedQuestion[];
pagination?: {
current: number;
next?: string;
other_pages?: Record<string, string>;
};
[key: string]: unknown;
}
18 changes: 15 additions & 3 deletions src/serpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
BaseResponse,
EngineParameters,
GetBySearchIdParameters,
GoogleSearchParameters,
GoogleSearchResponse,
LocationsApiParameters,
} from "./types.ts";
import { _internals } from "./utils.ts";
Expand All @@ -14,6 +16,14 @@ const LOCATIONS_PATH = "/locations.json";
const SEARCH_PATH = "/search";
const SEARCH_ARCHIVE_PATH = `/searches`;

/**
* Get typed JSON response for Google Search.
*/
export function getJson(
parameters: GoogleSearchParameters,
callback?: (json: GoogleSearchResponse) => void,
): Promise<GoogleSearchResponse>;

/**
* Get JSON response based on search parameters.
*
Expand Down Expand Up @@ -52,13 +62,15 @@ export function getJson(

export function getJson(
...args:
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
// deno-lint-ignore no-explicit-any
| [parameters: EngineParameters, callback?: (json: any) => void]
| [
engine: string,
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
// deno-lint-ignore no-explicit-any
callback?: (json: any) => void,
]
): Promise<BaseResponse> {
): Promise<BaseResponse | GoogleSearchResponse> {
if (typeof args[0] === "string" && typeof args[1] === "object") {
const [engine, parameters, callback] = args;
const newParameters = { ...parameters, engine } as EngineParameters;
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ export type EngineParameters = Record<string, any>;
// deno-lint-ignore no-explicit-any
export type BaseResponse = Record<string, any>;

export type {
GoogleSearchParameters,
GoogleSearchResponse,
OrganicResult,
KnowledgeGraph,
RelatedQuestion,
SearchInformation,
SearchMetadata,
SearchParameters,
} from "./engines/google.ts";

export type GetBySearchIdParameters = {
api_key?: string;
timeout?: number;
Expand Down
92 changes: 92 additions & 0 deletions tests/types_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Type-level tests for engine-specific types.
* These verify compile-time behavior — if this file fails to type-check,
* the types are broken.
*/
import type {
EngineParameters,
GoogleSearchParameters,
GoogleSearchResponse,
OrganicResult,
} from "../src/types.ts";
import { getJson } from "../src/serpapi.ts";
Copy link
Copy Markdown

@catherine-serpapi catherine-serpapi Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getJson is imported but I don't see it being used for any tests - could we remove if not needed

import {
describe,
it,
} from "https://deno.land/std@0.170.0/testing/bdd.ts";
import {
assertEquals,
} from "https://deno.land/std@0.170.0/testing/asserts.ts";

describe("GoogleSearchParameters", () => {
it("accepts valid Google Search params", () => {
const params: GoogleSearchParameters = {
engine: "google",
q: "coffee",
location: "Austin, Texas",
gl: "us",
hl: "en",
num: 10,
start: 0,
safe: "active",
device: "desktop",
};
assertEquals(params.engine, "google");
assertEquals(params.q, "coffee");
});

it("allows tbm search type values", () => {
const params: GoogleSearchParameters = {
engine: "google",
q: "coffee",
tbm: "nws",
};
assertEquals(params.tbm, "nws");
});
});

describe("GoogleSearchResponse", () => {
it("has correct shape", () => {
const response: GoogleSearchResponse = {
search_metadata: {
id: "123",
status: "Success",
json_endpoint: "https://serpapi.com/searches/123.json",
created_at: "2025-01-01",
processed_at: "2025-01-01",
google_url: "https://www.google.com/search?q=coffee",
raw_html_file: "https://serpapi.com/searches/123.html",
total_time_taken: 1.5,
},
search_parameters: {
engine: "google",
q: "coffee",
},
organic_results: [
{
position: 1,
title: "Coffee - Wikipedia",
link: "https://en.wikipedia.org/wiki/Coffee",
displayed_link: "en.wikipedia.org",
snippet: "Coffee is a brewed drink...",
},
],
};
assertEquals(response.search_metadata.status, "Success");

const firstResult: OrganicResult = response.organic_results![0];
assertEquals(firstResult.position, 1);
assertEquals(firstResult.title, "Coffee - Wikipedia");
});
});

describe("backwards compatibility", () => {
it("generic EngineParameters still works", () => {
const params: EngineParameters = {
engine: "bing",
q: "anything",
custom_field: 123,
};
assertEquals(params.engine, "bing");
});
});
Loading